From 3328e292418b60606d105bbf0c726d18dff4531b Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 1 Jul 2021 17:50:54 +0800 Subject: [PATCH] add flowy-ast to generate the protobuf files via rust struct --- .gitignore | 2 +- rust-lib/Cargo.toml | 2 + rust-lib/flowy-ast/Cargo.toml | 11 + rust-lib/flowy-ast/src/ast.rs | 236 +++++++++++++++++ rust-lib/flowy-ast/src/attr.rs | 423 +++++++++++++++++++++++++++++++ rust-lib/flowy-ast/src/ctxt.rs | 44 ++++ rust-lib/flowy-ast/src/lib.rs | 16 ++ rust-lib/flowy-ast/src/symbol.rs | 35 +++ rust-lib/flowy-ast/src/ty_ext.rs | 135 ++++++++++ 9 files changed, 903 insertions(+), 1 deletion(-) create mode 100644 rust-lib/flowy-ast/Cargo.toml create mode 100644 rust-lib/flowy-ast/src/ast.rs create mode 100644 rust-lib/flowy-ast/src/attr.rs create mode 100644 rust-lib/flowy-ast/src/ctxt.rs create mode 100644 rust-lib/flowy-ast/src/lib.rs create mode 100644 rust-lib/flowy-ast/src/symbol.rs create mode 100644 rust-lib/flowy-ast/src/ty_ext.rs diff --git a/.gitignore b/.gitignore index 67f6890827..884eddcb82 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk -/rust-lib/flowy-ast + /rust-lib/flowy-derive \ No newline at end of file diff --git a/rust-lib/Cargo.toml b/rust-lib/Cargo.toml index b2d90e77fb..429b676a21 100644 --- a/rust-lib/Cargo.toml +++ b/rust-lib/Cargo.toml @@ -5,6 +5,8 @@ members = [ "dart-ffi", "flowy-log", "flowy-user", + "flowy-ast", + ] [profile.dev] diff --git a/rust-lib/flowy-ast/Cargo.toml b/rust-lib/flowy-ast/Cargo.toml new file mode 100644 index 0000000000..7af2d51468 --- /dev/null +++ b/rust-lib/flowy-ast/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "flowy-ast" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"]} +quote = "1.0" +proc-macro2 = "1.0" \ No newline at end of file diff --git a/rust-lib/flowy-ast/src/ast.rs b/rust-lib/flowy-ast/src/ast.rs new file mode 100644 index 0000000000..e1f35c910b --- /dev/null +++ b/rust-lib/flowy-ast/src/ast.rs @@ -0,0 +1,236 @@ +use crate::{attr, ty_ext::*, AttrsContainer, Ctxt}; +use syn::{self, punctuated::Punctuated}; + +pub struct ASTContainer<'a> { + /// The struct or enum name (without generics). + pub ident: syn::Ident, + /// Attributes on the structure. + pub attrs: AttrsContainer, + /// The contents of the struct or enum. + pub data: ASTData<'a>, +} + +impl<'a> ASTContainer<'a> { + pub fn from_ast(cx: &Ctxt, ast: &'a syn::DeriveInput) -> Option> { + let attrs = AttrsContainer::from_ast(cx, ast); + // syn::DeriveInput + // 1. syn::DataUnion + // 2. syn::DataStruct + // 3. syn::DataEnum + let data = match &ast.data { + syn::Data::Struct(data) => { + // https://docs.rs/syn/1.0.48/syn/struct.DataStruct.html + let (style, fields) = struct_from_ast(cx, &data.fields); + ASTData::Struct(style, fields) + }, + syn::Data::Union(_) => { + cx.error_spanned_by(ast, "Does not support derive for unions"); + return None; + }, + syn::Data::Enum(data) => { + // https://docs.rs/syn/1.0.48/syn/struct.DataEnum.html + ASTData::Enum(enum_from_ast(cx, &data.variants)) + }, + }; + + let ident = ast.ident.clone(); + let item = ASTContainer { ident, attrs, data }; + Some(item) + } +} + +pub enum ASTData<'a> { + Struct(ASTStyle, Vec>), + Enum(Vec>), +} + +impl<'a> ASTData<'a> { + pub fn all_fields(&'a self) -> Box> + 'a> { + match self { + ASTData::Enum(variants) => { + Box::new(variants.iter().flat_map(|variant| variant.fields.iter())) + }, + ASTData::Struct(_, fields) => Box::new(fields.iter()), + } + } + + pub fn all_variants(&'a self) -> Box + 'a> { + match self { + ASTData::Enum(variants) => { + let iter = variants.iter().map(|variant| &variant.attrs); + Box::new(iter) + }, + ASTData::Struct(_, fields) => { + let iter = fields.iter().flat_map(|_| None); + Box::new(iter) + }, + } + } + + pub fn all_idents(&'a self) -> Box + 'a> { + match self { + ASTData::Enum(variants) => Box::new(variants.iter().map(|v| &v.ident)), + ASTData::Struct(_, fields) => { + let iter = fields.iter().flat_map(|f| match &f.member { + syn::Member::Named(ident) => Some(ident), + _ => None, + }); + Box::new(iter) + }, + } + } +} + +/// A variant of an enum. +pub struct ASTEnumVariant<'a> { + pub ident: syn::Ident, + pub attrs: attr::ASTEnumAttrVariant, + pub style: ASTStyle, + pub fields: Vec>, + pub original: &'a syn::Variant, +} + +impl<'a> ASTEnumVariant<'a> { + pub fn name(&self) -> String { self.ident.to_string() } +} + +pub enum BracketCategory { + Other, + Opt, + Vec, + Map((String, String)), +} + +pub struct ASTField<'a> { + pub member: syn::Member, + pub attrs: attr::ASTAttrField, + pub ty: &'a syn::Type, + pub original: &'a syn::Field, + pub bracket_ty: Option, + pub bracket_inner_ty: Option, + pub bracket_category: Option, +} + +impl<'a> ASTField<'a> { + pub fn new(cx: &Ctxt, field: &'a syn::Field, index: usize) -> Self { + let mut bracket_inner_ty = None; + let mut bracket_ty = None; + let mut bracket_category = Some(BracketCategory::Other); + match parse_ty(&field.ty) { + Some(inner) => { + match inner.primitive_ty { + PrimitiveTy::Map(map_info) => { + bracket_category = Some(BracketCategory::Map(( + map_info.key.clone(), + map_info.value.clone(), + ))) + }, + PrimitiveTy::Vec => { + bracket_category = Some(BracketCategory::Vec); + }, + PrimitiveTy::Opt => { + bracket_category = Some(BracketCategory::Opt); + }, + } + + match *inner.bracket_ty_info { + Some(bracketed_inner_ty) => { + bracket_inner_ty = Some(bracketed_inner_ty.ident.clone()); + bracket_ty = Some(inner.ident.clone()); + }, + None => { + bracket_ty = Some(inner.ident.clone()); + }, + } + }, + None => { + cx.error_spanned_by(&field.ty, "fail to get the ty inner type"); + }, + } + + ASTField { + member: match &field.ident { + Some(ident) => syn::Member::Named(ident.clone()), + None => syn::Member::Unnamed(index.into()), + }, + attrs: attr::ASTAttrField::from_ast(cx, index, field), + ty: &field.ty, + original: field, + bracket_ty, + bracket_inner_ty, + bracket_category, + } + } + + pub fn ty_as_str(&self) -> String { + match self.bracket_inner_ty { + Some(ref ty) => ty.to_string(), + None => self.bracket_ty.as_ref().unwrap().clone().to_string(), + } + } + + #[allow(dead_code)] + pub fn name(&self) -> Option { + if let syn::Member::Named(ident) = &self.member { + return Some(ident.clone()); + } else { + None + } + } + + pub fn is_option(&self) -> bool { attr::is_option(&self.ty) } +} + +#[derive(Copy, Clone)] +pub enum ASTStyle { + Struct, + /// Many unnamed fields. + Tuple, + /// One unnamed field. + NewType, + /// No fields. + Unit, +} + +pub fn struct_from_ast<'a>(cx: &Ctxt, fields: &'a syn::Fields) -> (ASTStyle, Vec>) { + match fields { + syn::Fields::Named(fields) => (ASTStyle::Struct, fields_from_ast(cx, &fields.named)), + syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + (ASTStyle::NewType, fields_from_ast(cx, &fields.unnamed)) + }, + syn::Fields::Unnamed(fields) => (ASTStyle::Tuple, fields_from_ast(cx, &fields.unnamed)), + syn::Fields::Unit => (ASTStyle::Unit, Vec::new()), + } +} + +pub fn enum_from_ast<'a>( + cx: &Ctxt, + variants: &'a Punctuated, +) -> Vec> { + variants + .iter() + .flat_map(|variant| { + let attrs = attr::ASTEnumAttrVariant::from_ast(cx, variant); + + let (style, fields) = struct_from_ast(cx, &variant.fields); + Some(ASTEnumVariant { + ident: variant.ident.clone(), + attrs, + style, + fields, + original: variant, + }) + }) + .collect() +} + +fn fields_from_ast<'a>( + cx: &Ctxt, + fields: &'a Punctuated, +) -> Vec> { + fields + .iter() + .enumerate() + .map(|(index, field)| ASTField::new(cx, field, index)) + .collect() +} diff --git a/rust-lib/flowy-ast/src/attr.rs b/rust-lib/flowy-ast/src/attr.rs new file mode 100644 index 0000000000..c83a3eaf92 --- /dev/null +++ b/rust-lib/flowy-ast/src/attr.rs @@ -0,0 +1,423 @@ +use crate::{symbol::*, Ctxt}; + +use quote::ToTokens; +use syn::{ + self, + parse::{self, Parse}, + Meta::{List, NameValue, Path}, + NestedMeta::{Lit, Meta}, +}; + +use proc_macro2::{Group, Span, TokenStream, TokenTree}; + +#[allow(dead_code)] +pub struct AttrsContainer { + name: String, + pb_struct_type: Option, + pb_enum_type: Option, +} + +impl AttrsContainer { + /// Extract out the `#[pb(...)]` attributes from an item. + pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self { + let mut pb_struct_type = ASTAttr::none(cx, PB_STRUCT); + let mut pb_enum_type = ASTAttr::none(cx, PB_ENUM); + for meta_item in item + .attrs + .iter() + .flat_map(|attr| get_meta_items(cx, attr)) + .flatten() + { + match &meta_item { + // Parse `#[pb(struct = "Type")] + Meta(NameValue(m)) if m.path == PB_STRUCT => { + if let Ok(into_ty) = parse_lit_into_ty(cx, PB_STRUCT, &m.lit) { + pb_struct_type.set_opt(&m.path, Some(into_ty)); + } + }, + + // Parse `#[pb(enum = "Type")] + Meta(NameValue(m)) if m.path == PB_ENUM => { + if let Ok(into_ty) = parse_lit_into_ty(cx, PB_ENUM, &m.lit) { + pb_enum_type.set_opt(&m.path, Some(into_ty)); + } + }, + + Meta(meta_item) => { + let path = meta_item + .path() + .into_token_stream() + .to_string() + .replace(' ', ""); + cx.error_spanned_by( + meta_item.path(), + format!("unknown pb container attribute `{}`", path), + ); + }, + + Lit(lit) => { + cx.error_spanned_by(lit, "unexpected literal in pb container attribute"); + }, + } + } + match &item.data { + syn::Data::Struct(_) => { + pb_struct_type.set_if_none(default_pb_type(&cx, &item.ident)); + }, + syn::Data::Enum(_) => { + pb_enum_type.set_if_none(default_pb_type(&cx, &item.ident)); + }, + _ => {}, + } + + AttrsContainer { + name: item.ident.to_string(), + pb_struct_type: pb_struct_type.get(), + pb_enum_type: pb_enum_type.get(), + } + } + + pub fn pb_struct_type(&self) -> Option<&syn::Type> { self.pb_struct_type.as_ref() } + + pub fn pb_enum_type(&self) -> Option<&syn::Type> { self.pb_enum_type.as_ref() } +} + +struct ASTAttr<'c, T> { + cx: &'c Ctxt, + name: Symbol, + tokens: TokenStream, + value: Option, +} + +impl<'c, T> ASTAttr<'c, T> { + fn none(cx: &'c Ctxt, name: Symbol) -> Self { + ASTAttr { + cx, + name, + tokens: TokenStream::new(), + value: None, + } + } + + fn set(&mut self, obj: A, value: T) { + let tokens = obj.into_token_stream(); + + if self.value.is_some() { + self.cx + .error_spanned_by(tokens, format!("duplicate attribute `{}`", self.name)); + } else { + self.tokens = tokens; + self.value = Some(value); + } + } + + fn set_opt(&mut self, obj: A, value: Option) { + if let Some(value) = value { + self.set(obj, value); + } + } + + fn set_if_none(&mut self, value: T) { + if self.value.is_none() { + self.value = Some(value); + } + } + + fn get(self) -> Option { self.value } + + #[allow(dead_code)] + fn get_with_tokens(self) -> Option<(TokenStream, T)> { + match self.value { + Some(v) => Some((self.tokens, v)), + None => None, + } + } +} + +pub struct ASTAttrField { + name: String, + pb_index: Option, + pb_one_of: bool, + skip_serializing: bool, + skip_deserializing: bool, + serialize_with: Option, + deserialize_with: Option, +} + +impl ASTAttrField { + /// Extract out the `#[pb(...)]` attributes from a struct field. + pub fn from_ast(cx: &Ctxt, index: usize, field: &syn::Field) -> Self { + let mut pb_index = ASTAttr::none(cx, PB_INDEX); + let mut pb_one_of = BoolAttr::none(cx, PB_ONE_OF); + let mut serialize_with = ASTAttr::none(cx, SERIALIZE_WITH); + let mut skip_serializing = BoolAttr::none(cx, SKIP_SERIALIZING); + let mut deserialize_with = ASTAttr::none(cx, DESERIALIZE_WITH); + let mut skip_deserializing = BoolAttr::none(cx, SKIP_DESERIALIZING); + + let ident = match &field.ident { + Some(ident) => ident.to_string(), + None => index.to_string(), + }; + + for meta_item in field + .attrs + .iter() + .flat_map(|attr| get_meta_items(cx, attr)) + .flatten() + { + match &meta_item { + // Parse `#[pb(skip)]` + Meta(Path(word)) if word == SKIP => { + skip_serializing.set_true(word); + skip_deserializing.set_true(word); + }, + + // Parse '#[pb(index = x)]' + Meta(NameValue(m)) if m.path == PB_INDEX => { + if let syn::Lit::Int(lit) = &m.lit { + pb_index.set(&m.path, lit.clone()); + } + }, + + // Parse `#[pb(one_of)]` + Meta(Path(path)) if path == PB_ONE_OF => { + pb_one_of.set_true(path); + }, + + // Parse `#[pb(serialize_with = "...")]` + Meta(NameValue(m)) if m.path == SERIALIZE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(cx, SERIALIZE_WITH, &m.lit) { + serialize_with.set(&m.path, path); + } + }, + + // Parse `#[pb(deserialize_with = "...")]` + Meta(NameValue(m)) if m.path == DESERIALIZE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(cx, DESERIALIZE_WITH, &m.lit) { + deserialize_with.set(&m.path, path); + } + }, + + Meta(meta_item) => { + let path = meta_item + .path() + .into_token_stream() + .to_string() + .replace(' ', ""); + cx.error_spanned_by( + meta_item.path(), + format!("unknown field attribute `{}`", path), + ); + }, + + Lit(lit) => { + cx.error_spanned_by(lit, "unexpected literal in pb field attribute"); + }, + } + } + + ASTAttrField { + name: ident.to_string().clone(), + pb_index: pb_index.get(), + pb_one_of: pb_one_of.get(), + skip_serializing: skip_serializing.get(), + skip_deserializing: skip_deserializing.get(), + serialize_with: serialize_with.get(), + deserialize_with: deserialize_with.get(), + } + } + + #[allow(dead_code)] + pub fn pb_index(&self) -> Option { + match self.pb_index { + Some(ref lit) => Some(lit.base10_digits().to_string()), + None => None, + } + } + + pub fn is_one_of(&self) -> bool { self.pb_one_of } + + pub fn serialize_with(&self) -> Option<&syn::ExprPath> { self.serialize_with.as_ref() } + + pub fn deserialize_with(&self) -> Option<&syn::ExprPath> { self.deserialize_with.as_ref() } + + pub fn skip_serializing(&self) -> bool { self.skip_serializing } + + pub fn skip_deserializing(&self) -> bool { self.skip_deserializing } +} + +pub enum Default { + /// Field must always be specified because it does not have a default. + None, + /// The default is given by `std::default::Default::default()`. + Default, + /// The default is given by this function. + Path(syn::ExprPath), +} + +#[derive(Debug, Clone)] +pub struct ASTEnumAttrVariant { + pub name: String, + pub value: String, +} + +impl ASTEnumAttrVariant { + pub fn from_ast(cx: &Ctxt, variant: &syn::Variant) -> Self { + let name = variant.ident.to_string(); + let mut value = String::new(); + if variant.discriminant.is_some() { + match variant.discriminant.as_ref().unwrap().1 { + syn::Expr::Lit(ref expr_list) => { + let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit { + int_value + } else { + unimplemented!() + }; + value = lit_int.base10_digits().to_string(); + }, + _ => {}, + } + } + ASTEnumAttrVariant { name, value } + } +} + +pub fn get_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result, ()> { + if attr.path != PB_ATTRS { + return Ok(Vec::new()); + } + + // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html + match attr.parse_meta() { + Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), + Ok(other) => { + cx.error_spanned_by(other, "expected #[pb(...)]"); + Err(()) + }, + Err(err) => { + cx.syn_error(err); + Err(()) + }, + } +} + +fn parse_lit_into_expr_path( + cx: &Ctxt, + attr_name: Symbol, + lit: &syn::Lit, +) -> Result { + let string = get_lit_str(cx, attr_name, lit)?; + parse_lit_str(string).map_err(|_| { + cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value())) + }) +} + +fn get_lit_str<'a>(cx: &Ctxt, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> { + if let syn::Lit::Str(lit) = lit { + Ok(lit) + } else { + cx.error_spanned_by( + lit, + format!( + "expected pb {} attribute to be a string: `{} = \"...\"`", + attr_name, attr_name + ), + ); + Err(()) + } +} + +fn parse_lit_into_ty(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result { + let string = get_lit_str(cx, attr_name, lit)?; + + parse_lit_str(string).map_err(|_| { + cx.error_spanned_by( + lit, + format!("failed to parse type: {} = {:?}", attr_name, string.value()), + ) + }) +} + +pub fn parse_lit_str(s: &syn::LitStr) -> parse::Result +where + T: Parse, +{ + let tokens = spanned_tokens(s)?; + syn::parse2(tokens) +} + +fn spanned_tokens(s: &syn::LitStr) -> parse::Result { + let stream = syn::parse_str(&s.value())?; + Ok(respan_token_stream(stream, s.span())) +} + +fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream { + stream + .into_iter() + .map(|token| respan_token_tree(token, span)) + .collect() +} + +fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { + if let TokenTree::Group(g) = &mut token { + *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); + } + token.set_span(span); + token +} + +fn default_pb_type(ctxt: &Ctxt, ident: &syn::Ident) -> syn::Type { + let take_ident = format!("{}", ident.to_string()); + let lit_str = syn::LitStr::new(&take_ident, ident.span()); + if let Ok(tokens) = spanned_tokens(&lit_str) { + if let Ok(pb_struct_ty) = syn::parse2(tokens) { + return pb_struct_ty; + } + } + ctxt.error_spanned_by( + ident, + format!("❌ Can't find {} protobuf struct", take_ident), + ); + panic!() +} + +#[allow(dead_code)] +pub fn is_option(ty: &syn::Type) -> bool { + let path = match ungroup(ty) { + syn::Type::Path(ty) => &ty.path, + _ => { + return false; + }, + }; + let seg = match path.segments.last() { + Some(seg) => seg, + None => { + return false; + }, + }; + let args = match &seg.arguments { + syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args, + _ => { + return false; + }, + }; + seg.ident == "Option" && args.len() == 1 +} + +#[allow(dead_code)] +pub fn ungroup(mut ty: &syn::Type) -> &syn::Type { + while let syn::Type::Group(group) = ty { + ty = &group.elem; + } + ty +} + +struct BoolAttr<'c>(ASTAttr<'c, ()>); + +impl<'c> BoolAttr<'c> { + fn none(cx: &'c Ctxt, name: Symbol) -> Self { BoolAttr(ASTAttr::none(cx, name)) } + + fn set_true(&mut self, obj: A) { self.0.set(obj, ()); } + + fn get(&self) -> bool { self.0.value.is_some() } +} diff --git a/rust-lib/flowy-ast/src/ctxt.rs b/rust-lib/flowy-ast/src/ctxt.rs new file mode 100644 index 0000000000..c9ab2c987e --- /dev/null +++ b/rust-lib/flowy-ast/src/ctxt.rs @@ -0,0 +1,44 @@ +use quote::ToTokens; +use std::{cell::RefCell, fmt::Display, thread}; +use syn; + +#[derive(Default)] +pub struct Ctxt { + errors: RefCell>>, +} + +impl Ctxt { + pub fn new() -> Self { + Ctxt { + errors: RefCell::new(Some(Vec::new())), + } + } + + pub fn error_spanned_by(&self, obj: A, msg: T) { + self.errors + .borrow_mut() + .as_mut() + .unwrap() + .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); + } + + pub fn syn_error(&self, err: syn::Error) { + self.errors.borrow_mut().as_mut().unwrap().push(err); + } + + pub fn check(self) -> Result<(), Vec> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(errors), + } + } +} + +impl Drop for Ctxt { + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); + } + } +} diff --git a/rust-lib/flowy-ast/src/lib.rs b/rust-lib/flowy-ast/src/lib.rs new file mode 100644 index 0000000000..a0d730709f --- /dev/null +++ b/rust-lib/flowy-ast/src/lib.rs @@ -0,0 +1,16 @@ +#[macro_use] +extern crate syn; + +#[macro_use] +extern crate quote; + +mod ast; +mod attr; +mod ctxt; +pub mod symbol; +pub mod ty_ext; + +pub use self::{symbol::*, ty_ext::*}; +pub use ast::*; +pub use attr::*; +pub use ctxt::Ctxt; diff --git a/rust-lib/flowy-ast/src/symbol.rs b/rust-lib/flowy-ast/src/symbol.rs new file mode 100644 index 0000000000..86af7d15b3 --- /dev/null +++ b/rust-lib/flowy-ast/src/symbol.rs @@ -0,0 +1,35 @@ +use std::fmt::{self, Display}; +use syn::{Ident, Path}; + +#[derive(Copy, Clone)] +pub struct Symbol(&'static str); +pub const PB_ATTRS: Symbol = Symbol("pb"); +pub const SKIP: Symbol = Symbol("skip"); //#[pb(skip)] +pub const PB_INDEX: Symbol = Symbol("index"); //#[pb(index = "1")] +pub const PB_ONE_OF: Symbol = Symbol("one_of"); //#[pb(one_of)] +pub const DESERIALIZE_WITH: Symbol = Symbol("deserialize_with"); +pub const SKIP_DESERIALIZING: Symbol = Symbol("skip_deserializing"); +pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with"); //#[pb(serialize_with = "...")] +pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); //#[pb(skip_serializing)] +pub const PB_STRUCT: Symbol = Symbol("struct"); //#[pb(struct="some struct")] +pub const PB_ENUM: Symbol = Symbol("enum"); //#[pb(enum="some enum")] + +impl PartialEq for Ident { + fn eq(&self, word: &Symbol) -> bool { self == word.0 } +} + +impl<'a> PartialEq for &'a Ident { + fn eq(&self, word: &Symbol) -> bool { *self == word.0 } +} + +impl PartialEq for Path { + fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) } +} + +impl<'a> PartialEq for &'a Path { + fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) } +} + +impl Display for Symbol { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(self.0) } +} diff --git a/rust-lib/flowy-ast/src/ty_ext.rs b/rust-lib/flowy-ast/src/ty_ext.rs new file mode 100644 index 0000000000..401f99bfe7 --- /dev/null +++ b/rust-lib/flowy-ast/src/ty_ext.rs @@ -0,0 +1,135 @@ +use crate::Ctxt; +use quote::format_ident; +use syn::{self, AngleBracketedGenericArguments, PathSegment}; + +#[derive(Eq, PartialEq, Debug)] +pub enum PrimitiveTy { + Map(MapInfo), + Vec, + Opt, +} + +#[derive(Debug)] +pub struct TyInfo<'a> { + pub ident: &'a syn::Ident, + pub ty: &'a syn::Type, + pub primitive_ty: PrimitiveTy, + pub bracket_ty_info: Box>>, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct MapInfo { + pub key: String, + pub value: String, +} + +impl MapInfo { + fn new(key: String, value: String) -> Self { MapInfo { key, value } } +} + +impl<'a> TyInfo<'a> { + #[allow(dead_code)] + pub fn bracketed_ident(&'a self) -> &'a syn::Ident { + match self.bracket_ty_info.as_ref() { + Some(b_ty) => b_ty.ident, + None => { + panic!() + }, + } + } +} + +pub fn parse_ty(ty: &syn::Type) -> Option { + // Type -> TypePath -> Path -> PathSegment -> PathArguments -> + // AngleBracketedGenericArguments -> GenericArgument -> Type. + if let syn::Type::Path(ref p) = ty { + if p.path.segments.len() != 1 { + return None; + } + + let seg = match p.path.segments.last() { + Some(seg) => seg, + None => return None, + }; + + return if let syn::PathArguments::AngleBracketed(ref bracketed) = seg.arguments { + match seg.ident.to_string().as_ref() { + "HashMap" => generate_hashmap_ty_info(ty, seg, bracketed), + "Vec" => generate_vec_ty_info(seg, bracketed), + _ => { + panic!("Unsupported ty") + }, + } + } else { + assert_eq!(seg.ident.to_string(), "Option".to_string()); + generate_option_ty_info(ty, seg) + }; + } + None +} + +fn parse_bracketed(bracketed: &AngleBracketedGenericArguments) -> Vec<&syn::Type> { + bracketed + .args + .iter() + .flat_map(|arg| { + if let syn::GenericArgument::Type(ref ty_in_bracket) = arg { + Some(ty_in_bracket) + } else { + None + } + }) + .collect::>() +} + +pub fn generate_hashmap_ty_info<'a>( + ty: &'a syn::Type, + path_segment: &'a PathSegment, + bracketed: &'a AngleBracketedGenericArguments, +) -> Option> { + // The args of map must greater than 2 + if bracketed.args.len() != 2 { + return None; + } + let types = parse_bracketed(bracketed); + let key = parse_ty(types[0]).unwrap().ident.to_string(); + let value = parse_ty(types[1]).unwrap().ident.to_string(); + let bracket_ty_info = Box::new(parse_ty(&types[1])); + return Some(TyInfo { + ident: &path_segment.ident, + ty, + primitive_ty: PrimitiveTy::Map(MapInfo::new(key, value)), + bracket_ty_info, + }); +} + +fn generate_option_ty_info<'a>( + ty: &'a syn::Type, + path_segment: &'a PathSegment, +) -> Option> { + return Some(TyInfo { + ident: &path_segment.ident, + ty, + primitive_ty: PrimitiveTy::Opt, + bracket_ty_info: Box::new(None), + }); +} + +fn generate_vec_ty_info<'a>( + path_segment: &'a PathSegment, + bracketed: &'a AngleBracketedGenericArguments, +) -> Option> { + if bracketed.args.len() != 1 { + return None; + } + if let syn::GenericArgument::Type(ref bracketed_type) = bracketed.args.first().unwrap() { + let bracketed_ty_info = Box::new(parse_ty(&bracketed_type)); + return Some(TyInfo { + ident: &path_segment.ident, + ty: bracketed_type, + primitive_ty: PrimitiveTy::Vec, + bracket_ty_info: bracketed_ty_info, + }); + } + return None; +}