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}