mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add flowy-ast to generate the protobuf files via rust struct
This commit is contained in:
parent
2d3e50c017
commit
3328e29241
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,5 +9,5 @@ Cargo.lock
|
|||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
/rust-lib/flowy-ast
|
|
||||||
/rust-lib/flowy-derive
|
/rust-lib/flowy-derive
|
@ -5,6 +5,8 @@ members = [
|
|||||||
"dart-ffi",
|
"dart-ffi",
|
||||||
"flowy-log",
|
"flowy-log",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
|
"flowy-ast",
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
11
rust-lib/flowy-ast/Cargo.toml
Normal file
11
rust-lib/flowy-ast/Cargo.toml
Normal file
@ -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
rust-lib/flowy-ast/src/ast.rs
Normal file
236
rust-lib/flowy-ast/src/ast.rs
Normal file
@ -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
rust-lib/flowy-ast/src/attr.rs
Normal file
423
rust-lib/flowy-ast/src/attr.rs
Normal file
@ -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
rust-lib/flowy-ast/src/ctxt.rs
Normal file
44
rust-lib/flowy-ast/src/ctxt.rs
Normal file
@ -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
rust-lib/flowy-ast/src/lib.rs
Normal file
16
rust-lib/flowy-ast/src/lib.rs
Normal file
@ -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
rust-lib/flowy-ast/src/symbol.rs
Normal file
35
rust-lib/flowy-ast/src/symbol.rs
Normal file
@ -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
rust-lib/flowy-ast/src/ty_ext.rs
Normal file
135
rust-lib/flowy-ast/src/ty_ext.rs
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user