summaryrefslogtreecommitdiffstats
path: root/types/_build.rs
diff options
context:
space:
mode:
Diffstat (limited to 'types/_build.rs')
-rw-r--r--types/_build.rs304
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");
+}