tl_codegen/
lib.rs

1extern crate pest;
2#[macro_use]
3extern crate pest_derive;
4#[macro_use]
5extern crate quote;
6
7use pest::Parser;
8use std::collections::HashMap;
9
10#[derive(Parser)]
11#[grammar = "tl.pest"]
12struct TlParser;
13const _GRAMMAR: &str = include_str!("tl.pest");
14
15fn capitalize(s: &str) -> String {
16    let mut v: Vec<char> = s.chars().collect();
17    v[0] = v[0].to_uppercase().nth(0).unwrap();
18    v.into_iter().collect()
19}
20
21fn convert_type(t: &str) -> String {
22    match t {
23        "double" => "f64".to_owned(),
24        "string" => "String".to_owned(),
25        "int32" => "i32".to_owned(),
26        "int53" => "i64".to_owned(),
27        "int64" => "i64".to_owned(),
28        "Bool" => "bool".to_owned(),
29        "bytes" => "String".to_owned(),
30        _ => capitalize(t),
31    }.to_owned()
32}
33
34fn convert_typeid(pair: pest::iterators::Pair<Rule>, res: &mut String) {
35    match pair.as_rule() {
36        Rule::vector => {
37            let inner = pair
38                .into_inner()
39                .next()
40                .unwrap()
41                .into_inner()
42                .next()
43                .unwrap();
44            res.push_str("Vec<");
45            convert_typeid(inner, res);
46            res.push_str(">");
47        }
48        Rule::ident => {
49            let ident = pair.as_str();
50            let ident = convert_type(ident);
51            res.push_str(&ident);
52        }
53        _ => unreachable!(),
54    };
55}
56
57fn render_param(
58    pair: pest::iterators::Pair<Rule>,
59    docinfo: &HashMap<String, ParamDocInfo>,
60    parent_class: &str,
61) -> quote::Tokens {
62    let mut pairs = pair.into_inner();
63    let mut name = pairs.next().unwrap().as_str().to_owned();
64    let docinfo = docinfo.get(&name).unwrap();
65    let typeid = pairs.next().unwrap();
66    let typeid = typeid.into_inner().next().unwrap();
67    let mut typeid_str = String::new();
68    convert_typeid(typeid, &mut typeid_str);
69    if typeid_str == parent_class {
70        typeid_str = format!("Box<{}>", typeid_str);
71    }
72    let typeid = typeid_str;
73    let mut pre = if name == "type" {
74        name.push('_');
75        quote! {
76            #[serde(rename="type")]
77        }
78    } else {
79        quote::Tokens::new()
80    };
81    let default_false = if typeid == "bool" {
82        quote!{
83            #[serde(default)]
84        }
85    } else {
86        quote::Tokens::new()
87    };
88    let serialize_number = if typeid == "i32" || typeid == "i64" {
89        quote!{
90            #[serde(deserialize_with="::serde_aux::field_attributes::deserialize_number_from_string")]
91        }
92    } else {
93        quote::Tokens::new()
94    };
95    let typeid = if docinfo.optional {
96        quote::Ident::new(format!("Option<{}>", typeid))
97    } else {
98        quote::Ident::new(typeid)
99    };
100    let name = quote::Ident::new(name);
101    let doc = docinfo.doc.replace("//-", " ");
102    pre.append(quote! {
103        #[doc = #doc]
104        #serialize_number
105        #default_false
106        pub #name:#typeid
107    });
108    pre
109}
110
111#[derive(Debug)]
112struct Class {
113    name: String,
114    types: Vec<String>,
115    doc: String,
116}
117
118fn render_type(
119    pair: pest::iterators::Pair<Rule>,
120    docinfo: TypeDocInfo,
121    classes: &mut HashMap<String, Class>,
122) -> quote::Tokens {
123    let mut pairs = pair.into_inner();
124    let name = pairs.next().unwrap().as_str();
125    let name_capitalized = quote::Ident::new(capitalize(name));
126    let params = pairs.next().unwrap();
127    let classname = capitalize(pairs.next().unwrap().as_str());
128    let params = params
129        .into_inner()
130        .map(|p| render_param(p, &docinfo.params, &classname))
131        .collect::<Vec<_>>();
132    let class = classes.entry(classname.clone()).or_insert_with(|| Class {
133        name: classname,
134        types: Vec::new(),
135        doc: String::new(),
136    });
137    class.types.push(capitalize(name));
138
139    let doc = docinfo.doc.replace("//-", " ");
140    quote! {
141        #[derive(Serialize, Deserialize, Debug, Clone)]
142        #[doc = #doc]
143        pub struct #name_capitalized {
144            #(#params),*
145        }
146    }
147}
148
149fn render_method(pair: pest::iterators::Pair<Rule>, docinfo: TypeDocInfo) -> quote::Tokens {
150    let mut pairs = pair.into_inner();
151    let name = pairs.next().unwrap().as_str();
152    let name_capitalized = capitalize(name);
153    let params = pairs.next().unwrap();
154    let params = params
155        .into_inner()
156        .map(|p| render_param(p, &docinfo.params, ""))
157        .collect::<Vec<_>>();
158    let name_ident = quote::Ident::new(name_capitalized);
159    let rettype = quote::Ident::new(convert_type(pairs.next().unwrap().as_str()));
160
161    let doc = docinfo.doc.replace("//-", " ");
162    quote! {
163        #[derive(Serialize, Deserialize, Debug, Clone)]
164        #[doc = #doc]
165        pub struct #name_ident {
166            #(#params),*
167        }
168        impl Method for #name_ident {
169            const TYPE: &'static str = #name;
170            type Response = #rettype;
171        }
172    }
173}
174
175fn render_class(class: Class) -> quote::Tokens {
176    let name = quote::Ident::new(class.name);
177    let types = class
178        .types
179        .into_iter()
180        .map(|t| quote::Ident::new(t))
181        .collect::<Vec<_>>();
182    if types.len() <= 1 {
183        return quote::Tokens::new();
184    }
185    let types2 = types.clone();
186    let doc = class.doc.replace("//-", " ");
187    quote! {
188        #[derive(Serialize, Deserialize, Debug, Clone)]
189        #[serde(rename_all="camelCase")]
190        #[serde(tag="@type")]
191        #[doc = #doc]
192        pub enum #name {
193            #(#types(#types2)),*
194        }
195    }
196}
197
198#[derive(Debug)]
199struct ParamDocInfo {
200    optional: bool,
201    doc: String,
202}
203#[derive(Debug)]
204struct TypeDocInfo {
205    doc: String,
206    params: HashMap<String, ParamDocInfo>,
207}
208
209fn extract_docinfo(
210    pair: pest::iterators::Pair<Rule>,
211    classes: &mut HashMap<String, Class>,
212) -> TypeDocInfo {
213    let mut params = HashMap::new();
214    let mut doc = String::new();
215    for p in pair.into_inner() {
216        let mut pairs = p.into_inner();
217        let name = pairs.next().unwrap().as_str();
218        let descr = pairs.next().unwrap().as_str();
219        if name == "description" {
220            doc = descr.to_owned();
221        } else if name == "class" {
222            classes.insert(
223                name.to_owned(),
224                Class {
225                    name: name.to_owned(),
226                    types: Vec::new(),
227                    doc: descr.to_owned(),
228                },
229            );
230        } else {
231            let optional = descr.contains("may be null")
232                || descr.contains("only available to bots")
233                || descr.contains("bots only")
234                || descr.contains("or null");
235            let n = if name == "param_description" {
236                "description"
237            } else {
238                name
239            };
240            params.insert(
241                n.to_owned(),
242                ParamDocInfo {
243                    optional,
244                    doc: descr.to_owned(),
245                },
246            );
247        }
248    }
249    TypeDocInfo { doc, params }
250}
251
252pub fn generate(src: &str) -> (String, String) {
253    let pairs = TlParser::parse(Rule::tl, &src).unwrap_or_else(|e| panic!("{}", e));
254
255    let mut functions = false;
256    let mut classes = HashMap::new();
257    let mut type_tokens = quote::Tokens::new();
258    let mut method_tokens = quote::Tokens::new();
259    for pair in pairs {
260        match pair.as_rule() {
261            Rule::section => {
262                functions = true;
263            }
264            Rule::definition => {
265                let mut pairs = pair.into_inner();
266                let docstring = pairs.next().unwrap();
267                let typedef = pairs.next().unwrap();
268                let docinfo = extract_docinfo(docstring, &mut classes);
269                if functions {
270                    method_tokens.append(render_method(typedef, docinfo));
271                } else {
272                    type_tokens.append(render_type(typedef, docinfo, &mut classes));
273                }
274            }
275            _ => {
276                unreachable!();
277            }
278        }
279    }
280    for (_, class) in classes.into_iter() {
281        type_tokens.append(render_class(class));
282    }
283    (type_tokens.as_str().to_owned(), method_tokens.as_str().to_owned())
284}