Browse Source

add flowy-ast to generate the protobuf files via rust struct

appflowy 4 năm trước cách đây
mục cha
commit
3328e29241

+ 1 - 1
.gitignore

@@ -9,5 +9,5 @@ Cargo.lock
 # These are backup files generated by rustfmt
 **/*.rs.bk
 
-/rust-lib/flowy-ast
+
 /rust-lib/flowy-derive

+ 2 - 0
rust-lib/Cargo.toml

@@ -5,6 +5,8 @@ members = [
   "dart-ffi",
   "flowy-log",
   "flowy-user",
+  "flowy-ast",
+
 ]
 
 [profile.dev]

+ 11 - 0
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"

+ 236 - 0
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<ASTContainer<'a>> {
+        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<ASTField<'a>>),
+    Enum(Vec<ASTEnumVariant<'a>>),
+}
+
+impl<'a> ASTData<'a> {
+    pub fn all_fields(&'a self) -> Box<dyn Iterator<Item = &'a ASTField<'a>> + '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<dyn Iterator<Item = &'a attr::ASTEnumAttrVariant> + '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<dyn Iterator<Item = &'a syn::Ident> + '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<ASTField<'a>>,
+    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<syn::Ident>,
+    pub bracket_inner_ty: Option<syn::Ident>,
+    pub bracket_category: Option<BracketCategory>,
+}
+
+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<syn::Ident> {
+        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<ASTField<'a>>) {
+    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<syn::Variant, Token![,]>,
+) -> Vec<ASTEnumVariant<'a>> {
+    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<syn::Field, Token![,]>,
+) -> Vec<ASTField<'a>> {
+    fields
+        .iter()
+        .enumerate()
+        .map(|(index, field)| ASTField::new(cx, field, index))
+        .collect()
+}

+ 423 - 0
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<syn::Type>,
+    pb_enum_type: Option<syn::Type>,
+}
+
+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<T>,
+}
+
+impl<'c, T> ASTAttr<'c, T> {
+    fn none(cx: &'c Ctxt, name: Symbol) -> Self {
+        ASTAttr {
+            cx,
+            name,
+            tokens: TokenStream::new(),
+            value: None,
+        }
+    }
+
+    fn set<A: ToTokens>(&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<A: ToTokens>(&mut self, obj: A, value: Option<T>) {
+        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<T> { 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<syn::LitInt>,
+    pb_one_of: bool,
+    skip_serializing: bool,
+    skip_deserializing: bool,
+    serialize_with: Option<syn::ExprPath>,
+    deserialize_with: Option<syn::ExprPath>,
+}
+
+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<String> {
+        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<Vec<syn::NestedMeta>, ()> {
+    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<syn::ExprPath, ()> {
+    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<syn::Type, ()> {
+    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<T>(s: &syn::LitStr) -> parse::Result<T>
+where
+    T: Parse,
+{
+    let tokens = spanned_tokens(s)?;
+    syn::parse2(tokens)
+}
+
+fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
+    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<A: ToTokens>(&mut self, obj: A) { self.0.set(obj, ()); }
+
+    fn get(&self) -> bool { self.0.value.is_some() }
+}

+ 44 - 0
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<Option<Vec<syn::Error>>>,
+}
+
+impl Ctxt {
+    pub fn new() -> Self {
+        Ctxt {
+            errors: RefCell::new(Some(Vec::new())),
+        }
+    }
+
+    pub fn error_spanned_by<A: ToTokens, T: Display>(&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<syn::Error>> {
+        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");
+        }
+    }
+}

+ 16 - 0
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;

+ 35 - 0
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<Symbol> for Ident {
+    fn eq(&self, word: &Symbol) -> bool { self == word.0 }
+}
+
+impl<'a> PartialEq<Symbol> for &'a Ident {
+    fn eq(&self, word: &Symbol) -> bool { *self == word.0 }
+}
+
+impl PartialEq<Symbol> for Path {
+    fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) }
+}
+
+impl<'a> PartialEq<Symbol> 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) }
+}

+ 135 - 0
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<Option<TyInfo<'a>>>,
+}
+
+#[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<TyInfo> {
+    // 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::<Vec<&syn::Type>>()
+}
+
+pub fn generate_hashmap_ty_info<'a>(
+    ty: &'a syn::Type,
+    path_segment: &'a PathSegment,
+    bracketed: &'a AngleBracketedGenericArguments,
+) -> Option<TyInfo<'a>> {
+    // 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<TyInfo<'a>> {
+    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<TyInfo<'a>> {
+    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;
+}