gull/codegen/
hack.rs

1use super::docs::{format_docstring, CommentStyle};
2use super::shared;
3use super::Codegen;
4use crate::prelude::*;
5use anyhow::Result;
6use convert_case::{Case, Casing};
7
8pub struct HackCodegen {
9    namespace: &'static str,
10}
11
12impl Codegen for HackCodegen {
13    fn gen_declarations(declarations: &Declarations) -> Result<String> {
14        let mut rc = HackCodegen { namespace: "" };
15
16        let mut declarations_code = String::from("<?hh\n");
17
18        for config in &declarations.config {
19            match config {
20                DeclarationsConfig::HackNamespace(namespace) => rc.namespace = namespace,
21                DeclarationsConfig::FileHeader(header) => {
22                    declarations_code.push_str(&format!("{}\n", header));
23                }
24            }
25        }
26
27        for declaration in &declarations.declarations {
28            declarations_code.push('\n');
29            declarations_code.push_str(&rc.gen_declaration(declaration)?);
30            declarations_code.push('\n');
31        }
32
33        Ok(declarations_code)
34    }
35}
36
37impl HackCodegen {
38    fn gen_declaration(&self, declaration: &TypeDeclaration) -> Result<String> {
39        let name = self.gen_namespaced_name(declaration.name);
40        let mut r = match &declaration.value {
41            DeclarationValue::TPrimitive(p) => {
42                format!(
43                    "type {}{} = {};",
44                    name,
45                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
46                    self.gen_primitive_type(p)
47                )
48            }
49            DeclarationValue::TMap(m) => {
50                format!(
51                    "type {}{} = {};",
52                    name,
53                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
54                    self.gen_map(m)
55                )
56            }
57            DeclarationValue::TVec(v) => {
58                format!(
59                    "type {}{} = {};",
60                    name,
61                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
62                    self.gen_vec(v)
63                )
64            }
65            DeclarationValue::TOption(o) => {
66                format!(
67                    "type {}{} = {};",
68                    name,
69                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
70                    self.gen_option(o)
71                )
72            }
73            DeclarationValue::TTuple(t) => {
74                format!(
75                    "type {}{} = {};",
76                    name,
77                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
78                    self.gen_tuple(t)
79                )
80            }
81            DeclarationValue::TStruct(s) => {
82                format!(
83                    "type {}{} = {};",
84                    name,
85                    shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
86                    self.gen_struct(s, 0)
87                )
88            }
89            DeclarationValue::TEnum(e) => self.gen_enum(&name, &declaration.generic_params, e),
90            DeclarationValue::TSimpleEnum(e) => self.gen_simple_enum(&name, &e.variants),
91            DeclarationValue::Docs => String::new(),
92            DeclarationValue::CodeBlock(b) => self.gen_code_block(b),
93        };
94
95        if let Some(doc) = format_docstring(declaration.docs, CommentStyle::DoubleSlash, 0) {
96            r = format!("{}\n{}", doc, r);
97        }
98
99        Ok(r)
100    }
101
102    fn gen_map(&self, m: &TMap) -> String {
103        let value = match &m.value {
104            TMapValue::TPrimitive(p) => self.gen_primitive_type(p),
105            TMapValue::TSet(s) => self.gen_set(s),
106        };
107
108        format!("dict<{}, {}>", self.gen_primitive_type(&m.key), value)
109    }
110
111    fn gen_vec(&self, v: &TVec) -> String {
112        let value = match &v {
113            TVec::TPrimitive(p) => self.gen_primitive_type(p),
114        };
115        format!("vec<{}>", value)
116    }
117
118    fn gen_set(&self, s: &TSet) -> String {
119        let value = match &s {
120            TSet::TPrimitive(p) => self.gen_primitive_type(p),
121        };
122
123        format!("keyset<{}>", value)
124    }
125
126    fn gen_option(&self, o: &TOption) -> String {
127        format!("?{}", self.gen_option_value(o))
128    }
129
130    fn gen_option_value(&self, o: &TOption) -> String {
131        match &o {
132            TOption::TPrimitive(p) => self.gen_primitive_type(&p),
133            TOption::TMap(m) => self.gen_map(m),
134            TOption::TVec(v) => self.gen_vec(v),
135            TOption::TSet(s) => self.gen_set(s),
136            TOption::TTuple(t) => self.gen_tuple(t),
137        }
138    }
139
140    fn gen_struct(&self, s: &TStruct, indent: usize) -> String {
141        let mut fields = String::new();
142
143        let prefix = " ".repeat(indent);
144
145        let mut is_option = "";
146
147        for field in &s.fields {
148            let mut field_type = match &field.field_type {
149                StructFieldType::TMap(m) => self.gen_map(m),
150                StructFieldType::TSet(s) => self.gen_set(s),
151                StructFieldType::TPrimitive(p) => self.gen_primitive_type(&p),
152                StructFieldType::TTuple(t) => self.gen_tuple(t),
153                StructFieldType::TVec(v) => self.gen_vec(v),
154                StructFieldType::TOption(o) => {
155                    is_option = "?";
156                    self.gen_option_value(o)
157                }
158            };
159
160            field_type = format!(
161                "\n    {}{}'{}' => {},",
162                &prefix, is_option, field.name, field_type
163            );
164            // reset option so that other fields after it don't all become options
165            is_option = "";
166
167            if let Some(doc) = format_docstring(field.docs, CommentStyle::DoubleSlash, indent + 4) {
168                field_type = format!("\n{}{}", doc, field_type);
169            }
170
171            fields.push_str(&field_type);
172        }
173
174        format!("shape({}\n{})", fields, prefix)
175    }
176
177    fn gen_simple_enum(&self, name: &str, variants: &[&str]) -> String {
178        let mut variant_lines = vec![];
179
180        for name in variants {
181            variant_lines.push(format!(
182                r#"    {} = "{}";{}"#,
183                name.to_case(Case::ScreamingSnake),
184                name,
185                "\n"
186            ));
187        }
188
189        format!(
190            "enum {}: string as string {{\n{}}}",
191            name,
192            variant_lines.join("")
193        )
194    }
195
196    fn gen_enum(&self, name: &str, generic_params: &[TGeneric], e: &TEnum) -> String {
197        let variant_type_enum_name = format!("{}Type", name);
198
199        let variant_names = e.variants.iter().map(|v| v.name).collect::<Vec<_>>();
200        let simple_enum = self.gen_simple_enum(&variant_type_enum_name, &variant_names);
201
202        let mut variants = String::new();
203
204        for variant in &e.variants {
205            let mut variant_type = match &variant.variant_type {
206                EnumVariantType::TStruct(s) => format!(" {}", self.gen_struct(s, 4)),
207                EnumVariantType::TPrimitive(p) => self.gen_primitive_type(p),
208            };
209
210            variant_type = format!("\n    ?'{}' => {},", variant.name, variant_type);
211
212            if let Some(doc) = format_docstring(variant.docs, CommentStyle::DoubleSlash, 4) {
213                variant_type = format!("\n{}{}", doc, variant_type);
214            }
215
216            variants.push_str(&variant_type);
217        }
218
219        format!(
220            "
221{}
222
223type {}{} = shape({}\n);",
224            simple_enum,
225            name,
226            shared::generic_params(generic_params, |g| self.gen_generic(g)),
227            variants
228        )
229    }
230
231    fn gen_tuple(&self, t: &TTuple) -> String {
232        let mut values = String::new();
233
234        for (n, item) in t.items.iter().enumerate() {
235            let is_last = n == t.items.len() - 1;
236
237            let value = match item {
238                TupleItem::TPrimitive(p) => self.gen_primitive_type(p).to_string(),
239                TupleItem::TOption(o) => self.gen_option(o),
240            };
241
242            values.push_str(&value);
243            if !is_last {
244                values.push_str(", ");
245            }
246        }
247
248        format!("({})", values)
249    }
250
251    fn gen_primitive_type(&self, ty: &TPrimitive) -> String {
252        match ty {
253            TPrimitive::String => "string".to_string(),
254            TPrimitive::Tbool => "bool".to_string(),
255            TPrimitive::Ti64 => "int".to_string(),
256            TPrimitive::Tf64 => "float".to_string(),
257            TPrimitive::Ti32 => "int".to_string(),
258            TPrimitive::Tu32 => "int".to_string(),
259            TPrimitive::Tusize => "int".to_string(),
260            TPrimitive::THardcoded(s) => s.to_string(),
261            TPrimitive::TVec(v) => self.gen_vec(v),
262            TPrimitive::TMap(m) => self.gen_map(m),
263            TPrimitive::TOption(o) => self.gen_option(o),
264            TPrimitive::TDifferentPerLanguage { hack, .. } => self.gen_primitive_type(&hack),
265            TPrimitive::TGeneric(g) => self.gen_generic(g),
266            TPrimitive::TReference(r) => {
267                format!(
268                    "{}{}",
269                    self.gen_namespaced_name(r.get_name()),
270                    shared::generic_params(&r.generic_params, |g| self.gen_generic(g))
271                )
272            }
273        }
274    }
275
276    // Get **namespaced** declaration/reference name
277    fn gen_namespaced_name(&self, name: &str) -> String {
278        format!("{}{}", self.namespace, name)
279    }
280
281    fn gen_generic(&self, g: &TGeneric) -> String {
282        match g {
283            TGeneric::TDefinition { name, .. } => name.to_string(),
284            TGeneric::TReference(r, ..) => {
285                self.gen_primitive_type(&TPrimitive::TReference(r.clone()))
286            }
287        }
288    }
289
290    fn gen_code_block(&self, b: &CodeBlock) -> String {
291        match b {
292            CodeBlock::Rust(_) => String::new(),
293            CodeBlock::Flow(_) => String::new(),
294            CodeBlock::Hack(lines) => lines
295                .iter()
296                .map(|s| s.to_string())
297                .collect::<Vec<_>>()
298                .join("\n"),
299        }
300    }
301}