diff options
Diffstat (limited to 'types/_build.rs')
-rw-r--r-- | types/_build.rs | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/types/_build.rs b/types/_build.rs new file mode 100644 index 0000000..4aefac0 --- /dev/null +++ b/types/_build.rs @@ -0,0 +1,304 @@ +use std::env; +use std::fs; +use std::path::Path; + +extern crate pest; +#[macro_use] +extern crate pest_derive; +#[macro_use] +extern crate quote; + +use pest::Parser; +use std::collections::HashMap; + +#[derive(Parser)] +#[grammar = "../tl.pest"] +struct TlParser; +const _GRAMMAR: &str = include_str!("tl.pest"); + +fn capitalize(s: &str) -> String { + let mut v: Vec<char> = s.chars().collect(); + v[0] = v[0].to_uppercase().nth(0).unwrap(); + v.into_iter().collect() +} + +fn convert_type(t: &str) -> String { + match t { + "double" => "f64".to_owned(), + "string" => "String".to_owned(), + "int32" => "i32".to_owned(), + "int53" => "i64".to_owned(), + "int64" => "i64".to_owned(), + "Bool" => "bool".to_owned(), + "bytes" => "String".to_owned(), + _ => capitalize(t), + }.to_owned() +} + +fn convert_typeid(pair: pest::iterators::Pair<Rule>, res: &mut String) { + match pair.as_rule() { + Rule::vector => { + let inner = pair + .into_inner() + .next() + .unwrap() + .into_inner() + .next() + .unwrap(); + res.push_str("Vec<"); + convert_typeid(inner, res); + res.push_str(">"); + } + Rule::ident => { + let ident = pair.as_str(); + let ident = convert_type(ident); + res.push_str(&ident); + } + _ => unreachable!(), + }; +} + +fn render_param( + pair: pest::iterators::Pair<Rule>, + docinfo: &HashMap<String, ParamDocInfo>, + parent_class: &str, +) -> quote::Tokens { + let mut pairs = pair.into_inner(); + let mut name = pairs.next().unwrap().as_str().to_owned(); + let docinfo = docinfo.get(&name).unwrap(); + let typeid = pairs.next().unwrap(); + let typeid = typeid.into_inner().next().unwrap(); + let mut typeid_str = String::new(); + convert_typeid(typeid, &mut typeid_str); + if typeid_str == parent_class { + typeid_str = format!("Box<{}>", typeid_str); + } + let typeid = typeid_str; + let mut pre = if name == "type" { + name.push('_'); + quote! { + #[serde(rename="type")] + } + } else { + quote::Tokens::new() + }; + let default_false = if typeid == "bool" { + quote!{ + #[serde(default)] + } + } else { + quote::Tokens::new() + }; + let serialize_number = if typeid == "i64" { + quote!{ + #[serde(deserialize_with="::serde_aux::field_attributes::deserialize_number_from_string")] + } + } else { + quote::Tokens::new() + }; + let typeid = if docinfo.optional { + quote::Ident::new(format!("Option<{}>", typeid)) + } else { + quote::Ident::new(typeid) + }; + let name = quote::Ident::new(name); + let doc = docinfo.doc.replace("//-", " "); + pre.append(quote! { + #[doc = #doc] + #serialize_number + #default_false + pub #name:#typeid + }); + pre +} + +#[derive(Debug)] +struct Class { + name: String, + types: Vec<String>, + doc: String, +} + +fn render_type( + pair: pest::iterators::Pair<Rule>, + docinfo: TypeDocInfo, + classes: &mut HashMap<String, Class>, +) -> quote::Tokens { + let mut pairs = pair.into_inner(); + let name = pairs.next().unwrap().as_str(); + let name_capitalized = quote::Ident::new(capitalize(name)); + let params = pairs.next().unwrap(); + let classname = capitalize(pairs.next().unwrap().as_str()); + let params = params + .into_inner() + .map(|p| render_param(p, &docinfo.params, &classname)) + .collect::<Vec<_>>(); + let class = classes.entry(classname.clone()).or_insert_with(|| Class { + name: classname, + types: Vec::new(), + doc: String::new(), + }); + class.types.push(capitalize(name)); + + let doc = docinfo.doc.replace("//-", " "); + quote! { + #[derive(Serialize, Deserialize, Debug, Clone)] + #[doc = #doc] + pub struct #name_capitalized { + #(#params),* + } + } +} + +fn render_method(pair: pest::iterators::Pair<Rule>, docinfo: TypeDocInfo) -> quote::Tokens { + let mut pairs = pair.into_inner(); + let name = pairs.next().unwrap().as_str(); + let name_capitalized = capitalize(name); + let params = pairs.next().unwrap(); + let params = params + .into_inner() + .map(|p| render_param(p, &docinfo.params, "")) + .collect::<Vec<_>>(); + let name_ident = quote::Ident::new(name_capitalized); + let rettype = quote::Ident::new(convert_type(pairs.next().unwrap().as_str())); + + let doc = docinfo.doc.replace("//-", " "); + quote! { + #[derive(Serialize, Deserialize, Debug, Clone)] + #[doc = #doc] + pub struct #name_ident { + #(#params),* + } + impl Method for #name_ident { + const TYPE: &'static str = #name; + type Response = #rettype; + } + } +} + +fn render_class(class: Class) -> quote::Tokens { + let name = quote::Ident::new(class.name); + let types = class + .types + .into_iter() + .map(|t| quote::Ident::new(t)) + .collect::<Vec<_>>(); + if types.len() <= 1 { + return quote::Tokens::new(); + } + let types2 = types.clone(); + let doc = class.doc.replace("//-", " "); + quote! { + #[derive(Serialize, Deserialize, Debug, Clone)] + #[serde(rename_all="camelCase")] + #[serde(tag="@type")] + #[doc = #doc] + pub enum #name { + #(#types(#types2)),* + } + } +} + +#[derive(Debug)] +struct ParamDocInfo { + optional: bool, + doc: String, +} +#[derive(Debug)] +struct TypeDocInfo { + doc: String, + params: HashMap<String, ParamDocInfo>, +} + +fn extract_docinfo( + pair: pest::iterators::Pair<Rule>, + classes: &mut HashMap<String, Class>, +) -> TypeDocInfo { + let mut params = HashMap::new(); + let mut doc = String::new(); + for p in pair.into_inner() { + let mut pairs = p.into_inner(); + let name = pairs.next().unwrap().as_str(); + let descr = pairs.next().unwrap().as_str(); + if name == "description" { + doc = descr.to_owned(); + } else if name == "class" { + classes.insert( + name.to_owned(), + Class { + name: name.to_owned(), + types: Vec::new(), + doc: descr.to_owned(), + }, + ); + } else { + let optional = descr.contains("may be null") + || descr.contains("only available to bots") + || descr.contains("bots only") + || descr.contains("or null"); + let n = if name == "param_description" { + "description" + } else { + name + }; + params.insert( + n.to_owned(), + ParamDocInfo { + optional, + doc: descr.to_owned(), + }, + ); + } + } + TypeDocInfo { doc, params } +} + +pub fn generate(src: &str) -> (String, String) { + let pairs = TlParser::parse(Rule::tl, &src).unwrap_or_else(|e| panic!("{}", e)); + + let mut functions = false; + let mut classes = HashMap::new(); + let mut type_tokens = quote::Tokens::new(); + let mut method_tokens = quote::Tokens::new(); + for pair in pairs { + match pair.as_rule() { + Rule::section => { + functions = true; + } + Rule::definition => { + let mut pairs = pair.into_inner(); + let docstring = pairs.next().unwrap(); + let typedef = pairs.next().unwrap(); + let docinfo = extract_docinfo(docstring, &mut classes); + if functions { + method_tokens.append(render_method(typedef, docinfo)); + } else { + type_tokens.append(render_type(typedef, docinfo, &mut classes)); + } + } + Rule::EOI => {}, + _ => { + unreachable!(); + } + } + } + for (_, class) in classes.into_iter() { + type_tokens.append(render_class(class)); + } + (type_tokens.as_str().to_owned(), method_tokens.as_str().to_owned()) +} + + +fn main() { + let src_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let types_dest_path = Path::new(&src_dir).join("src").join("tl_types.rs"); + let methods_dest_path = Path::new(&src_dir).join("src").join("tl_methods.rs"); + + let src_path = Path::new(&src_dir).join("td_api.tl"); + println!("cargo:rerun-if-changed={}",src_path.display()); + + let src = fs::read_to_string(src_path).expect("no td_api.tl file"); + let (t, m) = generate(&src); + fs::write(types_dest_path, t).expect("cannot write output file"); + fs::write(methods_dest_path, m).expect("cannot write output file"); +} |