diff --git a/shared-lib/flowy-ast/src/ast.rs b/shared-lib/flowy-ast/src/ast.rs index 031f31e8a9..140ecedbec 100644 --- a/shared-lib/flowy-ast/src/ast.rs +++ b/shared-lib/flowy-ast/src/ast.rs @@ -1,21 +1,24 @@ #![allow(clippy::all)] #![allow(unused_attributes)] #![allow(unused_assignments)] -use crate::{attr, ty_ext::*, ASTResult, AttrsContainer}; + +use crate::event_attrs::EventEnumAttrs; +use crate::node_attrs::NodeStructAttrs; +use crate::{is_recognizable_field, pb_attrs, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs}; 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, + pub attrs: PBAttrsContainer, /// The contents of the struct or enum. pub data: ASTData<'a>, } impl<'a> ASTContainer<'a> { pub fn from_ast(ast_result: &ASTResult, ast: &'a syn::DeriveInput) -> Option> { - let attrs = AttrsContainer::from_ast(ast_result, ast); + let attrs = PBAttrsContainer::from_ast(ast_result, ast); // syn::DeriveInput // 1. syn::DataUnion // 2. syn::DataStruct @@ -55,7 +58,7 @@ impl<'a> ASTData<'a> { } } - pub fn all_variants(&'a self) -> Box + 'a> { + pub fn all_variants(&'a self) -> Box + 'a> { match self { ASTData::Enum(variants) => { let iter = variants.iter().map(|variant| &variant.attrs); @@ -85,7 +88,7 @@ impl<'a> ASTData<'a> { /// A variant of an enum. pub struct ASTEnumVariant<'a> { pub ident: syn::Ident, - pub attrs: attr::ASTEnumAttrVariant, + pub attrs: EventEnumAttrs, pub style: ASTStyle, pub fields: Vec>, pub original: &'a syn::Variant, @@ -106,7 +109,8 @@ pub enum BracketCategory { pub struct ASTField<'a> { pub member: syn::Member, - pub attrs: attr::ASTAttrField, + pub pb_attrs: PBStructAttrs, + pub node_attrs: NodeStructAttrs, pub ty: &'a syn::Type, pub original: &'a syn::Field, pub bracket_ty: Option, @@ -161,7 +165,8 @@ impl<'a> ASTField<'a> { Some(ident) => syn::Member::Named(ident.clone()), None => syn::Member::Unnamed(index.into()), }, - attrs: attr::ASTAttrField::from_ast(cx, index, field), + pb_attrs: PBStructAttrs::from_ast(cx, index, field), + node_attrs: NodeStructAttrs::from_ast(cx, index, field), ty: &field.ty, original: field, bracket_ty, @@ -185,10 +190,6 @@ impl<'a> ASTField<'a> { None } } - - pub fn is_option(&self) -> bool { - attr::is_option(self.ty) - } } #[derive(Copy, Clone)] @@ -222,7 +223,7 @@ pub fn enum_from_ast<'a>( variants .iter() .flat_map(|variant| { - let attrs = attr::ASTEnumAttrVariant::from_ast(cx, ident, variant, enum_attrs); + let attrs = EventEnumAttrs::from_ast(cx, ident, variant, enum_attrs); let (style, fields) = struct_from_ast(cx, &variant.fields); Some(ASTEnumVariant { ident: variant.ident.clone(), @@ -239,6 +240,12 @@ fn fields_from_ast<'a>(cx: &ASTResult, fields: &'a Punctuated, + output: Option, + error_ty: Option, + pub ignore: bool, +} + +#[derive(Debug, Clone)] +pub struct EventEnumAttrs { + pub enum_name: String, + pub enum_item_name: String, + pub value: String, + pub event_attrs: EventAttrs, +} + +impl EventEnumAttrs { + pub fn from_ast( + ast_result: &ASTResult, + ident: &syn::Ident, + variant: &syn::Variant, + enum_attrs: &[syn::Attribute], + ) -> Self { + let enum_item_name = variant.ident.to_string(); + let enum_name = ident.to_string(); + let mut value = String::new(); + if variant.discriminant.is_some() { + if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 { + 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(); + } + } + let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs); + EventEnumAttrs { + enum_name, + enum_item_name, + value, + event_attrs, + } + } + + pub fn event_input(&self) -> Option { + self.event_attrs.input.clone() + } + + pub fn event_output(&self) -> Option { + self.event_attrs.output.clone() + } + + pub fn event_error(&self) -> String { + self.event_attrs.error_ty.as_ref().unwrap().clone() + } +} + +fn get_event_attrs_from( + ast_result: &ASTResult, + variant_attrs: &[syn::Attribute], + enum_attrs: &[syn::Attribute], +) -> EventAttrs { + let mut event_attrs = EventAttrs { + input: None, + output: None, + error_ty: None, + ignore: false, + }; + + enum_attrs + .iter() + .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR)) + .for_each(|attr| { + if let Ok(NameValue(named_value)) = attr.parse_meta() { + if let syn::Lit::Str(s) = named_value.lit { + event_attrs.error_ty = Some(s.value()); + } else { + eprintln!("❌ {} should not be empty", EVENT_ERR); + } + } else { + eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr); + } + }); + + let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item { + Meta(NameValue(name_value)) => { + if name_value.path == EVENT_INPUT { + if let syn::Lit::Str(s) = &name_value.lit { + let input_type = parse_lit_str(s) + .map_err(|_| { + ast_result + .error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value())) + }) + .unwrap(); + event_attrs.input = Some(input_type); + } + } + + if name_value.path == EVENT_OUTPUT { + if let syn::Lit::Str(s) = &name_value.lit { + let output_type = parse_lit_str(s) + .map_err(|_| { + ast_result + .error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value())) + }) + .unwrap(); + event_attrs.output = Some(output_type); + } + } + } + Meta(Path(word)) => { + if word == EVENT_IGNORE && attr.path == EVENT { + event_attrs.ignore = true; + } + } + Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"), + _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"), + }; + + let attr_meta_items_info = variant_attrs + .iter() + .flat_map(|attr| match get_event_meta_items(ast_result, attr) { + Ok(items) => Some((attr, items)), + Err(_) => None, + }) + .collect::)>>(); + + for (attr, nested_metas) in attr_meta_items_info { + nested_metas + .iter() + .for_each(|meta_item| extract_event_attr(attr, meta_item)) + } + + // eprintln!("😁{:#?}", event_attrs); + event_attrs +} diff --git a/shared-lib/flowy-ast/src/lib.rs b/shared-lib/flowy-ast/src/lib.rs index 757c34c033..5529177f04 100644 --- a/shared-lib/flowy-ast/src/lib.rs +++ b/shared-lib/flowy-ast/src/lib.rs @@ -5,12 +5,16 @@ extern crate syn; extern crate quote; mod ast; -mod attr; mod ctxt; +mod pb_attrs; +mod event_attrs; +mod node_attrs; pub mod symbol; pub mod ty_ext; + pub use self::{symbol::*, ty_ext::*}; pub use ast::*; -pub use attr::*; pub use ctxt::ASTResult; +pub use event_attrs::*; +pub use pb_attrs::*; diff --git a/shared-lib/flowy-ast/src/node_attrs.rs b/shared-lib/flowy-ast/src/node_attrs.rs new file mode 100644 index 0000000000..265f4aecee --- /dev/null +++ b/shared-lib/flowy-ast/src/node_attrs.rs @@ -0,0 +1,82 @@ +use crate::{get_node_meta_items, get_pb_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult}; +use proc_macro2::{Group, Span, TokenStream, TokenTree}; +use quote::ToTokens; +use syn::{ + self, + parse::{self, Parse}, + Meta::{List, NameValue, Path}, + NestedMeta::{Lit, Meta}, +}; + +pub struct NodeStructAttrs { + node_index: Option, + get_node_value_with: Option, + set_node_value_with: Option, +} + +impl NodeStructAttrs { + /// Extract out the `#[node(...)]` attributes from a struct field. + pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self { + let mut node_index = ASTAttr::none(ast_result, NODE_INDEX); + let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH); + let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH); + + 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_node_meta_items(ast_result, attr)) + .flatten() + { + match &meta_item { + // Parse '#[node(index = x)]' + Meta(NameValue(m)) if m.path == NODE_INDEX => { + if let syn::Lit::Int(lit) = &m.lit { + node_index.set(&m.path, lit.clone()); + } + } + + // Parse `#[node(get_node_value_with = "...")]` + Meta(NameValue(m)) if m.path == GET_NODE_VALUE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, GET_NODE_VALUE_WITH, &m.lit) { + get_node_value_with.set(&m.path, path); + } + } + + // Parse `#[node(set_node_value_with= "...")]` + Meta(NameValue(m)) if m.path == SET_NODE_VALUE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, SET_NODE_VALUE_WITH, &m.lit) { + set_node_value_with.set(&m.path, path); + } + } + + Meta(meta_item) => { + let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); + ast_result.error_spanned_by(meta_item.path(), format!("unknown node field attribute `{}`", path)); + } + + Lit(lit) => { + ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); + } + } + } + + NodeStructAttrs { + node_index: node_index.get(), + get_node_value_with: get_node_value_with.get(), + set_node_value_with: set_node_value_with.get(), + } + } + + pub fn set_node_value_with(&self) -> Option<&syn::ExprPath> { + self.set_node_value_with.as_ref() + } + + pub fn get_node_value_with(&self) -> Option<&syn::ExprPath> { + self.get_node_value_with.as_ref() + } +} diff --git a/shared-lib/flowy-ast/src/attr.rs b/shared-lib/flowy-ast/src/pb_attrs.rs similarity index 58% rename from shared-lib/flowy-ast/src/attr.rs rename to shared-lib/flowy-ast/src/pb_attrs.rs index 1eec119c81..708090ee81 100644 --- a/shared-lib/flowy-ast/src/attr.rs +++ b/shared-lib/flowy-ast/src/pb_attrs.rs @@ -1,5 +1,7 @@ #![allow(clippy::all)] + use crate::{symbol::*, ASTResult}; +use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{ self, @@ -8,16 +10,14 @@ use syn::{ NestedMeta::{Lit, Meta}, }; -use proc_macro2::{Group, Span, TokenStream, TokenTree}; - #[allow(dead_code)] -pub struct AttrsContainer { +pub struct PBAttrsContainer { name: String, pb_struct_type: Option, pb_enum_type: Option, } -impl AttrsContainer { +impl PBAttrsContainer { /// Extract out the `#[pb(...)]` attributes from an item. pub fn from_ast(ast_result: &ASTResult, item: &syn::DeriveInput) -> Self { let mut pb_struct_type = ASTAttr::none(ast_result, PB_STRUCT); @@ -25,7 +25,7 @@ impl AttrsContainer { for meta_item in item .attrs .iter() - .flat_map(|attr| get_meta_items(ast_result, attr)) + .flat_map(|attr| get_pb_meta_items(ast_result, attr)) .flatten() { match &meta_item { @@ -45,11 +45,11 @@ impl AttrsContainer { Meta(meta_item) => { let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); - ast_result.error_spanned_by(meta_item.path(), format!("unknown pb container attribute `{}`", path)); + ast_result.error_spanned_by(meta_item.path(), format!("unknown container attribute `{}`", path)); } Lit(lit) => { - ast_result.error_spanned_by(lit, "unexpected literal in pb container attribute"); + ast_result.error_spanned_by(lit, "unexpected literal in container attribute"); } } } @@ -63,7 +63,7 @@ impl AttrsContainer { _ => {} } - AttrsContainer { + PBAttrsContainer { name: item.ident.to_string(), pb_struct_type: pb_struct_type.get(), pb_enum_type: pb_enum_type.get(), @@ -79,7 +79,7 @@ impl AttrsContainer { } } -struct ASTAttr<'c, T> { +pub struct ASTAttr<'c, T> { ast_result: &'c ASTResult, name: Symbol, tokens: TokenStream, @@ -87,7 +87,7 @@ struct ASTAttr<'c, T> { } impl<'c, T> ASTAttr<'c, T> { - fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { + pub(crate) fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { ASTAttr { ast_result, name, @@ -96,7 +96,7 @@ impl<'c, T> ASTAttr<'c, T> { } } - fn set(&mut self, obj: A, value: T) { + pub(crate) fn set(&mut self, obj: A, value: T) { let tokens = obj.into_token_stream(); if self.value.is_some() { @@ -120,7 +120,7 @@ impl<'c, T> ASTAttr<'c, T> { } } - fn get(self) -> Option { + pub(crate) fn get(self) -> Option { self.value } @@ -133,26 +133,30 @@ impl<'c, T> ASTAttr<'c, T> { } } -pub struct ASTAttrField { +pub struct PBStructAttrs { #[allow(dead_code)] name: String, pb_index: Option, pb_one_of: bool, - skip_serializing: bool, - skip_deserializing: bool, - serialize_with: Option, - deserialize_with: Option, + skip_pb_serializing: bool, + skip_pb_deserializing: bool, + serialize_pb_with: Option, + deserialize_pb_with: Option, } -impl ASTAttrField { +pub fn is_recognizable_field(field: &syn::Field) -> bool { + field.attrs.iter().any(|attr| is_recognizable_attribute(attr)) +} + +impl PBStructAttrs { /// Extract out the `#[pb(...)]` attributes from a struct field. pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self { let mut pb_index = ASTAttr::none(ast_result, PB_INDEX); let mut pb_one_of = BoolAttr::none(ast_result, PB_ONE_OF); - let mut serialize_with = ASTAttr::none(ast_result, SERIALIZE_WITH); - let mut skip_serializing = BoolAttr::none(ast_result, SKIP_SERIALIZING); - let mut deserialize_with = ASTAttr::none(ast_result, DESERIALIZE_WITH); - let mut skip_deserializing = BoolAttr::none(ast_result, SKIP_DESERIALIZING); + let mut serialize_pb_with = ASTAttr::none(ast_result, SERIALIZE_PB_WITH); + let mut skip_pb_serializing = BoolAttr::none(ast_result, SKIP_PB_SERIALIZING); + let mut deserialize_pb_with = ASTAttr::none(ast_result, DESERIALIZE_PB_WITH); + let mut skip_pb_deserializing = BoolAttr::none(ast_result, SKIP_PB_DESERIALIZING); let ident = match &field.ident { Some(ident) => ident.to_string(), @@ -162,14 +166,14 @@ impl ASTAttrField { for meta_item in field .attrs .iter() - .flat_map(|attr| get_meta_items(ast_result, attr)) + .flat_map(|attr| get_pb_meta_items(ast_result, attr)) .flatten() { match &meta_item { // Parse `#[pb(skip)]` Meta(Path(word)) if word == SKIP => { - skip_serializing.set_true(word); - skip_deserializing.set_true(word); + skip_pb_serializing.set_true(word); + skip_pb_deserializing.set_true(word); } // Parse '#[pb(index = x)]' @@ -184,39 +188,39 @@ impl ASTAttrField { 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(ast_result, SERIALIZE_WITH, &m.lit) { - serialize_with.set(&m.path, path); + // Parse `#[pb(serialize_pb_with = "...")]` + Meta(NameValue(m)) if m.path == SERIALIZE_PB_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, SERIALIZE_PB_WITH, &m.lit) { + serialize_pb_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(ast_result, DESERIALIZE_WITH, &m.lit) { - deserialize_with.set(&m.path, path); + // Parse `#[pb(deserialize_pb_with = "...")]` + Meta(NameValue(m)) if m.path == DESERIALIZE_PB_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, DESERIALIZE_PB_WITH, &m.lit) { + deserialize_pb_with.set(&m.path, path); } } Meta(meta_item) => { let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); - ast_result.error_spanned_by(meta_item.path(), format!("unknown field attribute `{}`", path)); + ast_result.error_spanned_by(meta_item.path(), format!("unknown pb field attribute `{}`", path)); } Lit(lit) => { - ast_result.error_spanned_by(lit, "unexpected literal in pb field attribute"); + ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); } } } - ASTAttrField { + PBStructAttrs { name: ident, 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(), + skip_pb_serializing: skip_pb_serializing.get(), + skip_pb_deserializing: skip_pb_deserializing.get(), + serialize_pb_with: serialize_pb_with.get(), + deserialize_pb_with: deserialize_pb_with.get(), } } @@ -229,20 +233,20 @@ impl ASTAttrField { self.pb_one_of } - pub fn serialize_with(&self) -> Option<&syn::ExprPath> { - self.serialize_with.as_ref() + pub fn serialize_pb_with(&self) -> Option<&syn::ExprPath> { + self.serialize_pb_with.as_ref() } - pub fn deserialize_with(&self) -> Option<&syn::ExprPath> { - self.deserialize_with.as_ref() + pub fn deserialize_pb_with(&self) -> Option<&syn::ExprPath> { + self.deserialize_pb_with.as_ref() } - pub fn skip_serializing(&self) -> bool { - self.skip_serializing + pub fn skip_pb_serializing(&self) -> bool { + self.skip_pb_serializing } - pub fn skip_deserializing(&self) -> bool { - self.skip_deserializing + pub fn skip_pb_deserializing(&self) -> bool { + self.skip_pb_deserializing } } @@ -255,146 +259,13 @@ pub enum Default { Path(syn::ExprPath), } -#[derive(Debug, Clone)] -pub struct EventAttrs { - input: Option, - output: Option, - error_ty: Option, - pub ignore: bool, +pub fn is_recognizable_attribute(attr: &syn::Attribute) -> bool { + attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS } -#[derive(Debug, Clone)] -pub struct ASTEnumAttrVariant { - pub enum_name: String, - pub enum_item_name: String, - pub value: String, - pub event_attrs: EventAttrs, -} - -impl ASTEnumAttrVariant { - pub fn from_ast( - ast_result: &ASTResult, - ident: &syn::Ident, - variant: &syn::Variant, - enum_attrs: &[syn::Attribute], - ) -> Self { - let enum_item_name = variant.ident.to_string(); - let enum_name = ident.to_string(); - let mut value = String::new(); - if variant.discriminant.is_some() { - if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 { - 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(); - } - } - let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs); - ASTEnumAttrVariant { - enum_name, - enum_item_name, - value, - event_attrs, - } - } - - pub fn event_input(&self) -> Option { - self.event_attrs.input.clone() - } - - pub fn event_output(&self) -> Option { - self.event_attrs.output.clone() - } - - pub fn event_error(&self) -> String { - self.event_attrs.error_ty.as_ref().unwrap().clone() - } -} - -fn get_event_attrs_from( - ast_result: &ASTResult, - variant_attrs: &[syn::Attribute], - enum_attrs: &[syn::Attribute], -) -> EventAttrs { - let mut event_attrs = EventAttrs { - input: None, - output: None, - error_ty: None, - ignore: false, - }; - - enum_attrs - .iter() - .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR)) - .for_each(|attr| { - if let Ok(NameValue(named_value)) = attr.parse_meta() { - if let syn::Lit::Str(s) = named_value.lit { - event_attrs.error_ty = Some(s.value()); - } else { - eprintln!("❌ {} should not be empty", EVENT_ERR); - } - } else { - eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr); - } - }); - - let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item { - Meta(NameValue(name_value)) => { - if name_value.path == EVENT_INPUT { - if let syn::Lit::Str(s) = &name_value.lit { - let input_type = parse_lit_str(s) - .map_err(|_| { - ast_result - .error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value())) - }) - .unwrap(); - event_attrs.input = Some(input_type); - } - } - - if name_value.path == EVENT_OUTPUT { - if let syn::Lit::Str(s) = &name_value.lit { - let output_type = parse_lit_str(s) - .map_err(|_| { - ast_result - .error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value())) - }) - .unwrap(); - event_attrs.output = Some(output_type); - } - } - } - Meta(Path(word)) => { - if word == EVENT_IGNORE && attr.path == EVENT { - event_attrs.ignore = true; - } - } - Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"), - _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"), - }; - - let attr_meta_items_info = variant_attrs - .iter() - .flat_map(|attr| match get_meta_items(ast_result, attr) { - Ok(items) => Some((attr, items)), - Err(_) => None, - }) - .collect::)>>(); - - for (attr, nested_metas) in attr_meta_items_info { - nested_metas - .iter() - .for_each(|meta_item| extract_event_attr(attr, meta_item)) - } - - // eprintln!("😁{:#?}", event_attrs); - event_attrs -} - -pub fn get_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { - if attr.path != PB_ATTRS && attr.path != EVENT { +pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != PB_ATTRS { return Ok(vec![]); } @@ -402,7 +273,7 @@ pub fn get_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result Ok(meta.nested.into_iter().collect()), Ok(other) => { - cx.error_spanned_by(other, "expected #[pb(...)] or or #[event(...)]"); + cx.error_spanned_by(other, "expected #[pb(...)]"); Err(()) } Err(err) => { @@ -413,7 +284,52 @@ pub fn get_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result Result { +pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != NODE_ATTRS { + return Ok(vec![]); + } + + // 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 #[node(...)]"); + Err(()) + } + Err(err) => { + cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]"); + cx.syn_error(err); + Err(()) + } + } +} +pub fn get_event_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != EVENT { + return Ok(vec![]); + } + + // 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 #[event(...)]"); + Err(()) + } + Err(err) => { + cx.error_spanned_by(attr, "attribute must be str, e.g. #[event(xx = \"xxx\")]"); + cx.syn_error(err); + Err(()) + } + } +} + +pub fn parse_lit_into_expr_path( + ast_result: &ASTResult, + attr_name: Symbol, + lit: &syn::Lit, +) -> Result { let string = get_lit_str(ast_result, attr_name, lit)?; parse_lit_str(string) .map_err(|_| ast_result.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()))) diff --git a/shared-lib/flowy-ast/src/symbol.rs b/shared-lib/flowy-ast/src/symbol.rs index 64d57fa897..5fc05e3132 100644 --- a/shared-lib/flowy-ast/src/symbol.rs +++ b/shared-lib/flowy-ast/src/symbol.rs @@ -3,23 +3,43 @@ 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")] +// Protobuf +pub const PB_ATTRS: Symbol = Symbol("pb"); +//#[pb(skip)] +pub const SKIP: Symbol = Symbol("skip"); +//#[pb(index = "1")] +pub const PB_INDEX: Symbol = Symbol("index"); +//#[pb(one_of)] +pub const PB_ONE_OF: Symbol = Symbol("one_of"); +//#[pb(skip_pb_deserializing = "...")] +pub const SKIP_PB_DESERIALIZING: Symbol = Symbol("skip_pb_deserializing"); +//#[pb(skip_pb_serializing)] +pub const SKIP_PB_SERIALIZING: Symbol = Symbol("skip_pb_serializing"); +//#[pb(serialize_pb_with = "...")] +pub const SERIALIZE_PB_WITH: Symbol = Symbol("serialize_pb_with"); +//#[pb(deserialize_pb_with = "...")] +pub const DESERIALIZE_PB_WITH: Symbol = Symbol("deserialize_pb_with"); +//#[pb(struct="some struct")] +pub const PB_STRUCT: Symbol = Symbol("struct"); +//#[pb(enum="some enum")] +pub const PB_ENUM: Symbol = Symbol("enum"); + +// Event pub const EVENT_INPUT: Symbol = Symbol("input"); pub const EVENT_OUTPUT: Symbol = Symbol("output"); pub const EVENT_IGNORE: Symbol = Symbol("ignore"); pub const EVENT: Symbol = Symbol("event"); pub const EVENT_ERR: Symbol = Symbol("event_err"); +// Node +pub const NODE_ATTRS: Symbol = Symbol("node"); +pub const SKIP_NODE_ATTRS: Symbol = Symbol("skip_node_attribute"); +pub const GET_NODE_VALUE_WITH: Symbol = Symbol("get_value_with"); +pub const SET_NODE_VALUE_WITH: Symbol = Symbol("set_value_with"); +//#[node(index = "1")] +pub const NODE_INDEX: Symbol = Symbol("index"); + impl PartialEq for Ident { fn eq(&self, word: &Symbol) -> bool { self == word.0 diff --git a/shared-lib/flowy-codegen/src/dart_event/ast.rs b/shared-lib/flowy-codegen/src/dart_event/ast.rs index f0023a8391..1ae3a2bff4 100644 --- a/shared-lib/flowy-codegen/src/dart_event/ast.rs +++ b/shared-lib/flowy-codegen/src/dart_event/ast.rs @@ -1,4 +1,4 @@ -use flowy_ast::ASTEnumAttrVariant; +use flowy_ast::EventEnumAttrs; pub struct EventASTContext { pub event: syn::Ident, @@ -10,21 +10,21 @@ pub struct EventASTContext { } impl EventASTContext { - pub fn from(variant: &ASTEnumAttrVariant) -> EventASTContext { - let command_name = variant.enum_item_name.clone(); + pub fn from(enum_attrs: &EventEnumAttrs) -> EventASTContext { + let command_name = enum_attrs.enum_item_name.clone(); if command_name.is_empty() { - panic!("Invalid command name: {}", variant.enum_item_name); + panic!("Invalid command name: {}", enum_attrs.enum_item_name); } let event = format_ident!("{}", &command_name); let splits = command_name.split('_').collect::>(); - let event_ty = format_ident!("{}", variant.enum_name); + let event_ty = format_ident!("{}", enum_attrs.enum_name); let event_request_struct = format_ident!("{}Event", &splits.join("")); - let event_input = variant.event_input(); - let event_output = variant.event_output(); - let event_error = variant.event_error(); + let event_input = enum_attrs.event_input(); + let event_output = enum_attrs.event_output(); + let event_error = enum_attrs.event_error(); EventASTContext { event, diff --git a/shared-lib/flowy-codegen/src/dart_event/dart_event.rs b/shared-lib/flowy-codegen/src/dart_event/dart_event.rs index dee4144bcd..b06d76f943 100644 --- a/shared-lib/flowy-codegen/src/dart_event/dart_event.rs +++ b/shared-lib/flowy-codegen/src/dart_event/dart_event.rs @@ -130,7 +130,7 @@ pub fn parse_event_crate(event_crate: &DartEventCrate) -> Vec { .iter() .filter(|attr| !attr.attrs.event_attrs.ignore) .enumerate() - .map(|(_index, attr)| EventASTContext::from(&attr.attrs)) + .map(|(_index, variant)| EventASTContext::from(&variant.attrs)) .collect::>() } _ => vec![], diff --git a/shared-lib/flowy-codegen/src/protobuf_file/ast.rs b/shared-lib/flowy-codegen/src/protobuf_file/ast.rs index e14131a20a..fbe0f21b7c 100644 --- a/shared-lib/flowy-codegen/src/protobuf_file/ast.rs +++ b/shared-lib/flowy-codegen/src/protobuf_file/ast.rs @@ -66,7 +66,7 @@ fn parse_files_protobuf(proto_crate_path: &Path, proto_output_path: &Path) -> Ve s.fields .iter() - .filter(|field| field.attrs.pb_index().is_some()) + .filter(|field| field.pb_attrs.pb_index().is_some()) .for_each(|field| { ref_types.push(field.ty_as_str()); struct_template.set_field(field); @@ -121,7 +121,7 @@ pub fn get_ast_structs(ast: &syn::File) -> Vec { if let Item::Struct(item_struct) = item { let (_, fields) = struct_from_ast(&ast_result, &item_struct.fields); - if fields.iter().filter(|f| f.attrs.pb_index().is_some()).count() > 0 { + if fields.iter().filter(|f| f.pb_attrs.pb_index().is_some()).count() > 0 { proto_structs.push(Struct { name: item_struct.ident.to_string(), fields, diff --git a/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs index c84d1e8875..105c669403 100644 --- a/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs +++ b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs @@ -36,7 +36,7 @@ impl StructTemplate { pub fn set_field(&mut self, field: &ASTField) { // {{ field_type }} {{ field_name }} = {{index}}; let name = field.name().unwrap().to_string(); - let index = field.attrs.pb_index().unwrap(); + let index = field.pb_attrs.pb_index().unwrap(); let ty: &str = &field.ty_as_str(); let mut mapped_ty: &str = ty; diff --git a/shared-lib/flowy-derive/src/lib.rs b/shared-lib/flowy-derive/src/lib.rs index 71ff82b5ad..91baf7280f 100644 --- a/shared-lib/flowy-derive/src/lib.rs +++ b/shared-lib/flowy-derive/src/lib.rs @@ -9,6 +9,7 @@ use syn::{parse_macro_input, DeriveInput}; extern crate quote; mod dart_event; +mod node; mod proto_buf; // Inspired by https://serde.rs/attributes.html @@ -36,6 +37,12 @@ pub fn derive_dart_event(input: TokenStream) -> TokenStream { .into() } +#[proc_macro_derive(Node, attributes(node))] +pub fn derive_node(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + node::expand_derive(&input).unwrap_or_else(to_compile_errors).into() +} + fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { let compile_errors = errors.iter().map(syn::Error::to_compile_error); quote!(#(#compile_errors)*) diff --git a/shared-lib/flowy-derive/src/node/mod.rs b/shared-lib/flowy-derive/src/node/mod.rs new file mode 100644 index 0000000000..b201c5e8e0 --- /dev/null +++ b/shared-lib/flowy-derive/src/node/mod.rs @@ -0,0 +1,14 @@ +use flowy_ast::{ASTContainer, ASTResult}; +use proc_macro2::TokenStream; + +pub fn expand_derive(input: &syn::DeriveInput) -> Result> { + let ast_result = ASTResult::new(); + // let cont = match ASTContainer::from_ast(&ast_result, input) { + // Some(cont) => cont, + // None => return Err(ast_result.check().unwrap_err()), + // }; + + let mut token_stream: TokenStream = TokenStream::default(); + ast_result.check()?; + Ok(token_stream) +} diff --git a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs index 6308771527..f7b7a408cf 100644 --- a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -9,12 +9,12 @@ pub fn make_de_token_steam(ctxt: &ASTResult, ast: &ASTContainer) -> Option Optio let build_set_pb_fields = ast .data .all_fields() - .filter(|f| !f.attrs.skip_serializing()) + .filter(|f| !f.pb_attrs.skip_pb_serializing()) .flat_map(|field| se_token_stream_for_field(ast_result, field, false)); let se_token_stream: TokenStream = quote! { @@ -38,10 +38,10 @@ pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Optio } fn se_token_stream_for_field(ast_result: &ASTResult, field: &ASTField, _take: bool) -> Option { - if let Some(func) = &field.attrs.serialize_with() { + if let Some(func) = &field.pb_attrs.serialize_pb_with() { let member = &field.member; Some(quote! { pb.#member=o.#func(); }) - } else if field.attrs.is_one_of() { + } else if field.pb_attrs.is_one_of() { token_stream_for_one_of(ast_result, field) } else { gen_token_stream(ast_result, &field.member, field.ty, false) diff --git a/shared-lib/flowy-sync/src/client_folder/app_node.rs b/shared-lib/flowy-sync/src/client_folder/app_node.rs index bbff863c62..3882a65580 100644 --- a/shared-lib/flowy-sync/src/client_folder/app_node.rs +++ b/shared-lib/flowy-sync/src/client_folder/app_node.rs @@ -60,8 +60,8 @@ impl AppNode { get_attributes_str_value(self.tree.clone(), &self.path, "name") } - pub fn set_name(&self, name: &str) -> CollaborateResult<()> { - set_attributes_str_value(self.tree.clone(), &self.path, "name", name.to_string()) + pub fn set_name(&self, name: String) -> CollaborateResult<()> { + set_attributes_str_value(self.tree.clone(), &self.path, "name", name) } fn get_workspace_id(&self) -> Option { diff --git a/shared-lib/flowy-sync/src/client_folder/folder_node.rs b/shared-lib/flowy-sync/src/client_folder/folder_node.rs index 34099b1c8d..58cde82a17 100644 --- a/shared-lib/flowy-sync/src/client_folder/folder_node.rs +++ b/shared-lib/flowy-sync/src/client_folder/folder_node.rs @@ -11,8 +11,45 @@ use std::sync::Arc; pub type AtomicNodeTree = RwLock; +// pub struct FolderNodePad2 { +// tree: Arc, +// +// #[node(rename = "workspaces", revision = "WorkspaceRevision")] +// workspaces: Vec>, +// } +// +// impl FolderNodePad2 { +// pub fn get_workspace() {} +// pub fn get_mut_workspace() {} +// pub fn add_workspace() {} +// pub fn remove_workspace() {} +// pub fn to_json() {} +// } +// +// #[derive(Debug, Clone)] +// pub struct WorkspaceNode2 { +// tree: Arc, +// pub id: String, +// pub name: String, +// pub path: Path, +// } +// +// impl WorkspaceNode2 { +// pub fn get_id() {} +// pub fn set_id() {} +// pub fn get_name() {} +// pub fn set_name() {} +// pub fn get_apps() {} +// +// pub fn get_app() {} +// pub fn get_mut_app() {} +// pub fn add_app() {} +// pub fn remove_app() {} +// } + pub struct FolderNodePad { tree: Arc, + // name: workspaces, index of the node, workspaces: Vec>, trash: Vec>, } diff --git a/shared-lib/flowy-sync/src/client_folder/mod.rs b/shared-lib/flowy-sync/src/client_folder/mod.rs index 42269c2b61..fa0dde1ce9 100644 --- a/shared-lib/flowy-sync/src/client_folder/mod.rs +++ b/shared-lib/flowy-sync/src/client_folder/mod.rs @@ -4,6 +4,7 @@ mod folder_node; mod folder_pad; mod view_node; mod workspace_node; +mod workspace_node_2; pub use folder_node::*; pub use folder_pad::*; diff --git a/shared-lib/flowy-sync/src/client_folder/workspace_node_2.rs b/shared-lib/flowy-sync/src/client_folder/workspace_node_2.rs new file mode 100644 index 0000000000..143352f575 --- /dev/null +++ b/shared-lib/flowy-sync/src/client_folder/workspace_node_2.rs @@ -0,0 +1,12 @@ +use crate::client_folder::AtomicNodeTree; +use flowy_derive::Node; +use std::sync::Arc; + +#[derive(Debug, Clone, Node)] +pub struct WorkspaceNode2 { + tree: Arc, + #[node] + pub id: String, + // pub name: String, + // pub path: Path, +} diff --git a/shared-lib/flowy-sync/tests/client_folder/script.rs b/shared-lib/flowy-sync/tests/client_folder/script.rs index d330831c08..d5f6beda95 100644 --- a/shared-lib/flowy-sync/tests/client_folder/script.rs +++ b/shared-lib/flowy-sync/tests/client_folder/script.rs @@ -5,6 +5,7 @@ use std::sync::Arc; pub enum FolderNodePadScript { CreateApp { id: String, name: String }, DeleteApp { id: String }, + UpdateApp { id: String, name: String }, AssertApp { id: String, expected: Option }, AssertAppContent { id: String, name: String }, AssertNumberOfApps { expected: usize }, @@ -58,7 +59,12 @@ impl FolderNodePadTest { let workspace_node = Arc::make_mut(workspace_node); workspace_node.remove_app(&id); } - + FolderNodePadScript::UpdateApp { id, name } => { + let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); + let workspace_node = Arc::make_mut(workspace_node); + let app_node = Arc::make_mut(workspace_node.get_mut_app(&id).unwrap()); + app_node.set_name(name).unwrap(); + } FolderNodePadScript::AssertApp { id, expected } => { let workspace_node = self.folder_pad.get_workspace("1").unwrap(); let app = workspace_node.get_app(&id); diff --git a/shared-lib/flowy-sync/tests/client_folder/workspace_test.rs b/shared-lib/flowy-sync/tests/client_folder/workspace_test.rs index 281a251a71..3211a050e1 100644 --- a/shared-lib/flowy-sync/tests/client_folder/workspace_test.rs +++ b/shared-lib/flowy-sync/tests/client_folder/workspace_test.rs @@ -32,3 +32,22 @@ fn client_folder_delete_app_test() { }, ]); } + +#[test] +fn client_folder_update_app_test() { + let mut test = FolderNodePadTest::new(); + test.run_scripts(vec![ + CreateApp { + id: "1".to_string(), + name: "my first app".to_string(), + }, + UpdateApp { + id: "1".to_string(), + name: "TODO".to_string(), + }, + AssertAppContent { + id: "1".to_string(), + name: "TODO".to_string(), + }, + ]); +}