forked from libre-chip/fayalite
		
	initial public commit
This commit is contained in:
		
						commit
						0b958e7852
					
				
					 56 changed files with 30235 additions and 0 deletions
				
			
		
							
								
								
									
										613
									
								
								crates/fayalite-visit-gen/src/ast.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										613
									
								
								crates/fayalite-visit-gen/src/ast.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,613 @@ | |||
| // SPDX-License-Identifier: LGPL-3.0-or-later
 | ||||
| // See Notices.txt for copyright information
 | ||||
| use indexmap::IndexMap; | ||||
| use proc_macro2::{Span, TokenStream}; | ||||
| use quote::{IdentFragment, ToTokens, TokenStreamExt}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::{ | ||||
|     fmt::{self, Write}, | ||||
|     iter::FusedIterator, | ||||
|     str::FromStr, | ||||
| }; | ||||
| use thiserror::Error; | ||||
| 
 | ||||
| macro_rules! impl_try_from_str { | ||||
|     ($ty:ty) => { | ||||
|         impl TryFrom<&'_ str> for $ty { | ||||
|             type Error = <Self as FromStr>::Err; | ||||
| 
 | ||||
|             fn try_from(v: &str) -> Result<Self, Self::Error> { | ||||
|                 v.parse() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         impl TryFrom<String> for $ty { | ||||
|             type Error = <Self as FromStr>::Err; | ||||
| 
 | ||||
|             fn try_from(v: String) -> Result<Self, Self::Error> { | ||||
|                 v.parse() | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| pub struct Ident(pub String); | ||||
| 
 | ||||
| impl ToTokens for Ident { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         syn::Ident::from(self).to_tokens(tokens); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl IdentFragment for Ident { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.write_str(&self.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Ident { | ||||
|     pub fn is_start_char(ch: char) -> bool { | ||||
|         ch == '_' || ch.is_ascii_alphabetic() | ||||
|     } | ||||
|     pub fn is_continue_char(ch: char) -> bool { | ||||
|         ch == '_' || ch.is_ascii_alphanumeric() | ||||
|     } | ||||
|     pub fn is_ident(v: &str) -> bool { | ||||
|         !v.is_empty() | ||||
|             && v.starts_with(Self::is_start_char) | ||||
|             && v.trim_start_matches(Self::is_continue_char).is_empty() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Ident> for Path { | ||||
|     fn from(value: Ident) -> Self { | ||||
|         Path(value.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Ident> for String { | ||||
|     fn from(value: Ident) -> Self { | ||||
|         value.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Ident> for syn::Ident { | ||||
|     fn from(value: Ident) -> Self { | ||||
|         From::from(&value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&'_ Ident> for syn::Ident { | ||||
|     fn from(value: &Ident) -> Self { | ||||
|         syn::Ident::new(&value.0, Span::call_site()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, Error)] | ||||
| #[error("invalid identifier")] | ||||
| pub struct IdentParseError; | ||||
| 
 | ||||
| impl_try_from_str!(Ident); | ||||
| 
 | ||||
| impl FromStr for Ident { | ||||
|     type Err = IdentParseError; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         if Self::is_ident(s) { | ||||
|             Ok(Self(s.into())) | ||||
|         } else { | ||||
|             Err(IdentParseError) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| 
 | ||||
| pub struct Path(String); | ||||
| 
 | ||||
| impl Path { | ||||
|     pub fn iter(&self) -> PathIter<'_> { | ||||
|         PathIter(&self.0) | ||||
|     } | ||||
|     pub fn last(&self) -> Ident { | ||||
|         self.iter().next_back().unwrap() | ||||
|     } | ||||
|     pub fn is_path(s: &str) -> bool { | ||||
|         if s.is_empty() { | ||||
|             false | ||||
|         } else { | ||||
|             s.split("::").all(Ident::is_ident) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct PathIter<'a>(&'a str); | ||||
| 
 | ||||
| impl Iterator for PathIter<'_> { | ||||
|     type Item = Ident; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         if self.0.is_empty() { | ||||
|             None | ||||
|         } else if let Some((first, rest)) = self.0.split_once("::") { | ||||
|             self.0 = rest; | ||||
|             Some(Ident(first.into())) | ||||
|         } else { | ||||
|             let retval = self.0; | ||||
|             self.0 = &self.0[..0]; | ||||
|             Some(Ident(retval.into())) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn last(mut self) -> Option<Self::Item> { | ||||
|         self.next_back() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FusedIterator for PathIter<'_> {} | ||||
| 
 | ||||
| impl DoubleEndedIterator for PathIter<'_> { | ||||
|     fn next_back(&mut self) -> Option<Self::Item> { | ||||
|         if self.0.is_empty() { | ||||
|             None | ||||
|         } else if let Some((rest, last)) = self.0.rsplit_once("::") { | ||||
|             self.0 = rest; | ||||
|             Some(Ident(last.into())) | ||||
|         } else { | ||||
|             let retval = self.0; | ||||
|             self.0 = &self.0[..0]; | ||||
|             Some(Ident(retval.into())) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for Path { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         tokens.append_separated(self.iter(), <syn::Token![::]>::default()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, Error)] | ||||
| #[error("invalid path")] | ||||
| pub struct PathParseError; | ||||
| 
 | ||||
| impl From<Path> for String { | ||||
|     fn from(value: Path) -> Self { | ||||
|         value.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl_try_from_str!(Path); | ||||
| 
 | ||||
| impl FromStr for Path { | ||||
|     type Err = PathParseError; | ||||
| 
 | ||||
|     fn from_str(value: &str) -> Result<Self, Self::Err> { | ||||
|         if value.is_empty() { | ||||
|             Err(PathParseError) | ||||
|         } else if value.split("::").all(Ident::is_ident) { | ||||
|             Ok(Self(value.into())) | ||||
|         } else { | ||||
|             Err(PathParseError) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
| pub struct Definitions { | ||||
|     pub types: std::collections::BTreeMap<Path, Definition>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
| pub struct Definition { | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub fn_name_suffix: Option<Ident>, | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub generics: Option<Generics>, | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub fold_where: Option<WherePredicates>, | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub visit_where: Option<WherePredicates>, | ||||
|     pub data: Data, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
| #[serde(tag = "$kind")] | ||||
| pub enum Data { | ||||
|     ManualImpl, | ||||
|     Opaque, | ||||
|     Enum(Variants), | ||||
|     Struct(Fields), | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| pub struct FieldNameIdent { | ||||
|     pub ident: Ident, | ||||
|     pub is_getter: bool, | ||||
| } | ||||
| 
 | ||||
| impl FieldNameIdent { | ||||
|     pub fn to_member(&self) -> Option<syn::Member> { | ||||
|         let Self { | ||||
|             ref ident, | ||||
|             is_getter, | ||||
|         } = *self; | ||||
|         if is_getter { | ||||
|             None | ||||
|         } else { | ||||
|             Some(syn::Ident::from(ident).into()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for FieldNameIdent { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         let Self { | ||||
|             ref ident, | ||||
|             is_getter, | ||||
|         } = *self; | ||||
|         ident.to_tokens(tokens); | ||||
|         if is_getter { | ||||
|             syn::token::Paren::default().surround(tokens, |_| {}); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<FieldNameIdent> for String { | ||||
|     fn from(value: FieldNameIdent) -> Self { | ||||
|         let mut retval = value.ident.0; | ||||
|         if value.is_getter { | ||||
|             retval.push_str("()"); | ||||
|         } | ||||
|         retval | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, Error)] | ||||
| #[error("invalid field name")] | ||||
| pub struct FieldNameParseError; | ||||
| 
 | ||||
| impl_try_from_str!(FieldNameIdent); | ||||
| 
 | ||||
| impl FromStr for FieldNameIdent { | ||||
|     type Err = FieldNameParseError; | ||||
| 
 | ||||
|     fn from_str(value: &str) -> Result<Self, Self::Err> { | ||||
|         let ident = value.strip_suffix("()"); | ||||
|         let is_getter = ident.is_some(); | ||||
|         let ident = ident.unwrap_or(value); | ||||
|         if let Ok(ident) = ident.parse() { | ||||
|             Ok(Self { ident, is_getter }) | ||||
|         } else { | ||||
|             Err(FieldNameParseError) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| pub struct WherePredicates(pub syn::punctuated::Punctuated<syn::WherePredicate, syn::Token![,]>); | ||||
| 
 | ||||
| #[derive(Debug, Error)] | ||||
| #[error("invalid `where` predicates")] | ||||
| pub struct WherePredicatesParseError; | ||||
| 
 | ||||
| impl_try_from_str!(WherePredicates); | ||||
| 
 | ||||
| impl FromStr for WherePredicates { | ||||
|     type Err = WherePredicatesParseError; | ||||
| 
 | ||||
|     fn from_str(value: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(Self( | ||||
|             syn::parse::Parser::parse_str(syn::punctuated::Punctuated::parse_terminated, value) | ||||
|                 .map_err(|_| WherePredicatesParseError)?, | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<WherePredicates> for String { | ||||
|     fn from(value: WherePredicates) -> Self { | ||||
|         value.0.into_token_stream().to_string() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<WherePredicates> for syn::WhereClause { | ||||
|     fn from(value: WherePredicates) -> Self { | ||||
|         syn::WhereClause { | ||||
|             where_token: Default::default(), | ||||
|             predicates: value.0, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<syn::WhereClause> for WherePredicates { | ||||
|     fn from(value: syn::WhereClause) -> Self { | ||||
|         Self(value.predicates) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for WherePredicates { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         self.0.to_tokens(tokens); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize)] | ||||
| #[serde(untagged)] | ||||
| enum SerializedGenerics { | ||||
|     Where { | ||||
|         generics: String, | ||||
|         #[serde(rename = "where")] | ||||
|         where_predicates: WherePredicates, | ||||
|     }, | ||||
|     NoWhere(String), | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] | ||||
| #[serde(into = "SerializedGenerics", try_from = "SerializedGenerics")] | ||||
| pub struct Generics(pub syn::Generics); | ||||
| 
 | ||||
| impl ToTokens for Generics { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         self.0.to_tokens(tokens); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Generics> for SerializedGenerics { | ||||
|     fn from(mut value: Generics) -> Self { | ||||
|         match value.0.where_clause.take() { | ||||
|             Some(where_clause) => Self::Where { | ||||
|                 generics: value.0.into_token_stream().to_string(), | ||||
|                 where_predicates: where_clause.into(), | ||||
|             }, | ||||
|             None => Self::NoWhere(value.0.into_token_stream().to_string()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Error)] | ||||
| #[error("invalid generics")] | ||||
| pub struct GenericsParseError; | ||||
| 
 | ||||
| impl TryFrom<SerializedGenerics> for Generics { | ||||
|     type Error = GenericsParseError; | ||||
| 
 | ||||
|     fn try_from(value: SerializedGenerics) -> Result<Self, Self::Error> { | ||||
|         let (generics, where_clause) = match value { | ||||
|             SerializedGenerics::Where { | ||||
|                 generics, | ||||
|                 where_predicates, | ||||
|             } => (generics, Some(where_predicates.into())), | ||||
|             SerializedGenerics::NoWhere(generics) => (generics, None), | ||||
|         }; | ||||
|         let Ok(mut generics) = syn::parse_str::<syn::Generics>(&generics) else { | ||||
|             return Err(GenericsParseError); | ||||
|         }; | ||||
|         generics.where_clause = where_clause; | ||||
|         Ok(Self(generics)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| pub struct PathWithGenerics { | ||||
|     pub path: Path, | ||||
|     pub generics: Option<syn::AngleBracketedGenericArguments>, | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for PathWithGenerics { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         let Self { path, generics } = self; | ||||
|         path.to_tokens(tokens); | ||||
|         if let Some(generics) = generics { | ||||
|             <syn::Token![::]>::default().to_tokens(tokens); | ||||
|             generics.to_tokens(tokens); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<PathWithGenerics> for String { | ||||
|     fn from(value: PathWithGenerics) -> Self { | ||||
|         let PathWithGenerics { path, generics } = value; | ||||
|         let mut retval = String::from(path); | ||||
|         if let Some(generics) = generics { | ||||
|             write!(retval, "{}", generics.to_token_stream()).unwrap(); | ||||
|         } | ||||
|         retval | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, Error)] | ||||
| #[error("invalid path with optional generics")] | ||||
| pub struct PathWithGenericsParseError; | ||||
| 
 | ||||
| impl_try_from_str!(PathWithGenerics); | ||||
| 
 | ||||
| impl FromStr for PathWithGenerics { | ||||
|     type Err = PathWithGenericsParseError; | ||||
| 
 | ||||
|     fn from_str(value: &str) -> Result<Self, Self::Err> { | ||||
|         let (path, generics) = if let Some(lt_pos) = value.find('<') { | ||||
|             let (path, generics) = value.split_at(lt_pos); | ||||
|             let path = path.strip_suffix("::").unwrap_or(path); | ||||
|             match syn::parse_str(generics) { | ||||
|                 Ok(generics) => (path, Some(generics)), | ||||
|                 Err(_) => return Err(PathWithGenericsParseError), | ||||
|             } | ||||
|         } else { | ||||
|             (value, None) | ||||
|         }; | ||||
|         if let Ok(path) = path.parse() { | ||||
|             Ok(Self { path, generics }) | ||||
|         } else { | ||||
|             Err(PathWithGenericsParseError) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] | ||||
| #[serde(into = "String", try_from = "String")] | ||||
| pub enum FieldName { | ||||
|     Index(usize), | ||||
|     Ident(FieldNameIdent), | ||||
| } | ||||
| 
 | ||||
| impl FieldName { | ||||
|     pub fn to_member(&self) -> Option<syn::Member> { | ||||
|         match self { | ||||
|             &FieldName::Index(index) => Some(index.into()), | ||||
|             FieldName::Ident(ident) => ident.to_member(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for FieldName { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         match self { | ||||
|             &FieldName::Index(index) => syn::Index::from(index).to_tokens(tokens), | ||||
|             FieldName::Ident(ident) => ident.to_tokens(tokens), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<FieldName> for String { | ||||
|     fn from(value: FieldName) -> Self { | ||||
|         match value { | ||||
|             FieldName::Index(index) => index.to_string(), | ||||
|             FieldName::Ident(ident) => ident.into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl_try_from_str!(FieldName); | ||||
| 
 | ||||
| impl FromStr for FieldName { | ||||
|     type Err = FieldNameParseError; | ||||
| 
 | ||||
|     fn from_str(value: &str) -> Result<Self, Self::Err> { | ||||
|         if !value.is_empty() | ||||
|             && value | ||||
|                 .trim_start_matches(|ch: char| ch.is_ascii_digit()) | ||||
|                 .is_empty() | ||||
|         { | ||||
|             if let Ok(index) = value.parse() { | ||||
|                 Ok(Self::Index(index)) | ||||
|             } else { | ||||
|                 Err(FieldNameParseError) | ||||
|             } | ||||
|         } else { | ||||
|             value.parse().map(Self::Ident) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
| pub struct Fields { | ||||
|     #[serde(
 | ||||
|         default, | ||||
|         rename = "$constructor", | ||||
|         skip_serializing_if = "Option::is_none" | ||||
|     )] | ||||
|     pub constructor: Option<PathWithGenerics>, | ||||
|     #[serde(flatten)] | ||||
|     pub fields: IndexMap<FieldName, Field>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
| #[serde(transparent)] | ||||
| pub struct Variants { | ||||
|     pub variants: IndexMap<Ident, Option<Field>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] | ||||
| pub enum Field { | ||||
|     Opaque, | ||||
|     Visible, | ||||
|     RefVisible, | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::ast; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_serialize() { | ||||
|         let definitions = ast::Definitions { | ||||
|             types: FromIterator::from_iter([ | ||||
|                 ( | ||||
|                     ast::Path("Module".into()), | ||||
|                     ast::Definition { | ||||
|                         fn_name_suffix: None, | ||||
|                         generics: Some( | ||||
|                             ast::SerializedGenerics::Where { | ||||
|                                 generics: "<T: BundleValue>".into(), | ||||
|                                 where_predicates: "T::Type: BundleType<Value = T>," | ||||
|                                     .parse() | ||||
|                                     .unwrap(), | ||||
|                             } | ||||
|                             .try_into() | ||||
|                             .unwrap(), | ||||
|                         ), | ||||
|                         fold_where: None, | ||||
|                         visit_where: None, | ||||
|                         data: ast::Data::Struct(ast::Fields { | ||||
|                             constructor: Some("Module::new_unchecked".parse().unwrap()), | ||||
|                             fields: FromIterator::from_iter([( | ||||
|                                 "name_id()".parse().unwrap(), | ||||
|                                 ast::Field::Visible, | ||||
|                             )]), | ||||
|                         }), | ||||
|                     }, | ||||
|                 ), | ||||
|                 ( | ||||
|                     ast::Path("NameId".into()), | ||||
|                     ast::Definition { | ||||
|                         fn_name_suffix: None, | ||||
|                         generics: None, | ||||
|                         fold_where: None, | ||||
|                         visit_where: None, | ||||
|                         data: ast::Data::Struct(ast::Fields { | ||||
|                             constructor: None, | ||||
|                             fields: FromIterator::from_iter([ | ||||
|                                 ("0".try_into().unwrap(), ast::Field::Opaque), | ||||
|                                 ("1".try_into().unwrap(), ast::Field::Opaque), | ||||
|                             ]), | ||||
|                         }), | ||||
|                     }, | ||||
|                 ), | ||||
|             ]), | ||||
|         }; | ||||
|         let definitions_str = serde_json::to_string_pretty(&definitions).unwrap(); | ||||
|         println!("{definitions_str}"); | ||||
|         assert_eq!( | ||||
|             definitions_str, | ||||
|             r#"{
 | ||||
|   "types": { | ||||
|     "Module": { | ||||
|       "generics": { | ||||
|         "generics": "< T : BundleValue >", | ||||
|         "where": "T :: Type : BundleType < Value = T > ," | ||||
|       }, | ||||
|       "data": { | ||||
|         "$kind": "Struct", | ||||
|         "$constructor": "Module::new_unchecked", | ||||
|         "name_id()": "Visible" | ||||
|       } | ||||
|     }, | ||||
|     "NameId": { | ||||
|       "data": { | ||||
|         "$kind": "Struct", | ||||
|         "0": "Opaque", | ||||
|         "1": "Opaque" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }"#
 | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										426
									
								
								crates/fayalite-visit-gen/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								crates/fayalite-visit-gen/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,426 @@ | |||
| // SPDX-License-Identifier: LGPL-3.0-or-later
 | ||||
| // See Notices.txt for copyright information
 | ||||
| use proc_macro2::{Span, TokenStream}; | ||||
| use quote::{format_ident, quote, ToTokens}; | ||||
| use std::{collections::BTreeMap, fs}; | ||||
| use syn::{fold::Fold, parse_quote}; | ||||
| 
 | ||||
| pub mod ast; | ||||
| 
 | ||||
| fn map_camel_case_to_snake_case(s: &str) -> String { | ||||
|     #[derive(Clone, Copy, PartialEq, Eq)] | ||||
|     enum State { | ||||
|         Start, | ||||
|         Lowercase, | ||||
|         PushedUpper(char), | ||||
|     } | ||||
|     let mut state = State::Start; | ||||
|     let mut retval = String::new(); | ||||
|     for ch in s.chars() { | ||||
|         state = match ch { | ||||
|             'A'..='Z' => { | ||||
|                 match state { | ||||
|                     State::Start => {} | ||||
|                     State::Lowercase => retval.push('_'), | ||||
|                     State::PushedUpper(upper) => retval.push(upper.to_ascii_lowercase()), | ||||
|                 } | ||||
|                 State::PushedUpper(ch) | ||||
|             } | ||||
|             _ => { | ||||
|                 match state { | ||||
|                     State::PushedUpper(upper) => { | ||||
|                         retval.push(upper.to_ascii_lowercase()); | ||||
|                     } | ||||
|                     State::Start | State::Lowercase => {} | ||||
|                 } | ||||
|                 retval.push(ch); | ||||
|                 State::Lowercase | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     match state { | ||||
|         State::Lowercase | State::Start => {} | ||||
|         State::PushedUpper(upper) => retval.push(upper.to_ascii_lowercase()), | ||||
|     } | ||||
|     retval | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| struct DefinitionState { | ||||
|     fn_name_suffix: syn::Ident, | ||||
|     generics: syn::Generics, | ||||
|     fold_generics: syn::Generics, | ||||
|     folder_generics: syn::Generics, | ||||
|     visit_generics: syn::Generics, | ||||
|     visitor_generics: syn::Generics, | ||||
| } | ||||
| 
 | ||||
| impl DefinitionState { | ||||
|     fn folder_fn_name(&self) -> syn::Ident { | ||||
|         format_ident!("fold_{}", self.fn_name_suffix) | ||||
|     } | ||||
|     fn visitor_fn_name(&self) -> syn::Ident { | ||||
|         format_ident!("visit_{}", self.fn_name_suffix) | ||||
|     } | ||||
|     fn folder_fn(&self, path: &ast::Path) -> TokenStream { | ||||
|         let folder_fn_name = self.folder_fn_name(); | ||||
|         let (impl_generics, type_generics, where_clause) = self.folder_generics.split_for_impl(); | ||||
|         quote! { | ||||
|             fn #folder_fn_name #impl_generics(&mut self, v: #path #type_generics) -> Result<#path #type_generics, Self::Error> #where_clause { | ||||
|                 Fold::default_fold(v, self) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn visitor_fn(&self, path: &ast::Path) -> TokenStream { | ||||
|         let visitor_fn_name = self.visitor_fn_name(); | ||||
|         let (impl_generics, type_generics, where_clause) = self.visitor_generics.split_for_impl(); | ||||
|         quote! { | ||||
|             fn #visitor_fn_name #impl_generics(&mut self, v: &#path #type_generics) -> Result<(), Self::Error> #where_clause { | ||||
|                 Visit::default_visit(v, self) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn fold_impl(&self, path: &ast::Path, body: impl ToTokens) -> TokenStream { | ||||
|         let folder_fn_name = self.folder_fn_name(); | ||||
|         let (_, self_type_generics, _) = self.generics.split_for_impl(); | ||||
|         let (trait_impl_generics, _, trait_where_clause) = self.fold_generics.split_for_impl(); | ||||
|         quote! { | ||||
|             #[automatically_derived] | ||||
|             #[allow(clippy::init_numbered_fields)] | ||||
|             impl #trait_impl_generics Fold<State> for #path #self_type_generics #trait_where_clause { | ||||
|                 fn fold(self, state: &mut State) -> Result<Self, State::Error> { | ||||
|                     state.#folder_fn_name(self) | ||||
|                 } | ||||
|                 fn default_fold(self, state: &mut State) -> Result<Self, State::Error> { | ||||
|                     #body | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn visit_impl(&self, path: &ast::Path, body: impl ToTokens) -> TokenStream { | ||||
|         let visitor_fn_name = self.visitor_fn_name(); | ||||
|         let (_, self_type_generics, _) = self.generics.split_for_impl(); | ||||
|         let (trait_impl_generics, _, trait_where_clause) = self.visit_generics.split_for_impl(); | ||||
|         quote! { | ||||
|             #[automatically_derived] | ||||
|             impl #trait_impl_generics Visit<State> for #path #self_type_generics #trait_where_clause { | ||||
|                 fn visit(&self, state: &mut State) -> Result<(), State::Error> { | ||||
|                     state.#visitor_fn_name(self) | ||||
|                 } | ||||
|                 fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { | ||||
|                     #body | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct GenerateState<'a> { | ||||
|     def_states: BTreeMap<&'a ast::Path, DefinitionState>, | ||||
|     definitions: &'a ast::Definitions, | ||||
| } | ||||
| 
 | ||||
| struct MapStateToSelf; | ||||
| 
 | ||||
| impl syn::fold::Fold for MapStateToSelf { | ||||
|     fn fold_ident(&mut self, i: syn::Ident) -> syn::Ident { | ||||
|         if i == "State" { | ||||
|             syn::Ident::new("Self", i.span()) | ||||
|         } else { | ||||
|             i | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> GenerateState<'a> { | ||||
|     fn make_definition_state(&mut self, path: &'a ast::Path) -> syn::Result<()> { | ||||
|         let ast::Definition { | ||||
|             fn_name_suffix, | ||||
|             generics, | ||||
|             fold_where, | ||||
|             visit_where, | ||||
|             data: _, | ||||
|         } = self.definitions.types.get(path).ok_or_else(|| { | ||||
|             syn::Error::new( | ||||
|                 Span::call_site(), | ||||
|                 format!("can't find named type: {path:?}"), | ||||
|             ) | ||||
|         })?; | ||||
|         let fn_name_suffix = fn_name_suffix | ||||
|             .as_ref() | ||||
|             .map(syn::Ident::from) | ||||
|             .unwrap_or_else(|| format_ident!("{}", map_camel_case_to_snake_case(&path.last().0))); | ||||
|         let generics = generics.clone().map(|v| v.0).unwrap_or_default(); | ||||
|         let mut fold_generics = generics.clone(); | ||||
|         let mut folder_generics = generics.clone(); | ||||
|         fold_generics | ||||
|             .params | ||||
|             .insert(0, parse_quote! {State: ?Sized + Folder}); | ||||
|         if let Some(fold_where) = fold_where { | ||||
|             fold_generics | ||||
|                 .make_where_clause() | ||||
|                 .predicates | ||||
|                 .extend(fold_where.0.iter().cloned()); | ||||
|             folder_generics.make_where_clause().predicates.extend( | ||||
|                 fold_where | ||||
|                     .0 | ||||
|                     .iter() | ||||
|                     .cloned() | ||||
|                     .map(|v| MapStateToSelf.fold_where_predicate(v)), | ||||
|             ); | ||||
|         } | ||||
|         let mut visit_generics = generics.clone(); | ||||
|         let mut visitor_generics = generics.clone(); | ||||
|         visit_generics | ||||
|             .params | ||||
|             .insert(0, parse_quote! {State: ?Sized + Visitor}); | ||||
|         if let Some(visit_where) = visit_where { | ||||
|             visit_generics | ||||
|                 .make_where_clause() | ||||
|                 .predicates | ||||
|                 .extend(visit_where.0.iter().cloned()); | ||||
|             visitor_generics.make_where_clause().predicates.extend( | ||||
|                 visit_where | ||||
|                     .0 | ||||
|                     .iter() | ||||
|                     .cloned() | ||||
|                     .map(|v| MapStateToSelf.fold_where_predicate(v)), | ||||
|             ); | ||||
|         } | ||||
|         self.def_states.insert( | ||||
|             path, | ||||
|             DefinitionState { | ||||
|                 fn_name_suffix, | ||||
|                 generics, | ||||
|                 fold_generics, | ||||
|                 folder_generics, | ||||
|                 visit_generics, | ||||
|                 visitor_generics, | ||||
|             }, | ||||
|         ); | ||||
|         Ok(()) | ||||
|     } | ||||
|     fn new(ast: &'a ast::Definitions) -> syn::Result<Self> { | ||||
|         let mut retval = GenerateState { | ||||
|             def_states: BTreeMap::new(), | ||||
|             definitions: ast, | ||||
|         }; | ||||
|         let ast::Definitions { types } = ast; | ||||
|         for path in types.keys() { | ||||
|             retval.make_definition_state(path)?; | ||||
|         } | ||||
|         Ok(retval) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn generate(ast: &ast::Definitions) -> syn::Result<String> { | ||||
|     let state = GenerateState::new(ast)?; | ||||
|     let mut visitor_fns = vec![]; | ||||
|     let mut visit_impls = vec![]; | ||||
|     let mut folder_fns = vec![]; | ||||
|     let mut fold_impls = vec![]; | ||||
|     for (&def_path, def_state) in state.def_states.iter() { | ||||
|         folder_fns.push(def_state.folder_fn(def_path)); | ||||
|         visitor_fns.push(def_state.visitor_fn(def_path)); | ||||
|         let fold_body; | ||||
|         let visit_body; | ||||
|         let ast::Definition { | ||||
|             fn_name_suffix: _, | ||||
|             generics: _, | ||||
|             fold_where: _, | ||||
|             visit_where: _, | ||||
|             data, | ||||
|         } = ast.types.get(def_path).unwrap(); | ||||
|         match data { | ||||
|             ast::Data::ManualImpl => { | ||||
|                 continue; | ||||
|             } | ||||
|             ast::Data::Opaque => { | ||||
|                 fold_body = quote! { | ||||
|                     let _ = state; | ||||
|                     Ok(self) | ||||
|                 }; | ||||
|                 visit_body = quote! { | ||||
|                     let _ = state; | ||||
|                     Ok(()) | ||||
|                 }; | ||||
|             } | ||||
|             ast::Data::Struct(ast::Fields { | ||||
|                 constructor, | ||||
|                 fields, | ||||
|             }) => { | ||||
|                 let mut visit_members = vec![]; | ||||
|                 let mut fold_members = vec![]; | ||||
|                 for (field_name, field) in fields { | ||||
|                     let fold_member_name = if constructor.is_some() { | ||||
|                         None | ||||
|                     } else { | ||||
|                         let member = field_name.to_member(); | ||||
|                         if member.is_none() { | ||||
|                             return Err(syn::Error::new(Span::call_site(), format!("struct must have `$constructor` since it contains a non-plain field: {def_path:?} {field_name:?}"))); | ||||
|                         } | ||||
|                         member | ||||
|                     }; | ||||
|                     let fold_member_name = fold_member_name.as_slice(); | ||||
|                     let fold_member = match field { | ||||
|                         ast::Field::Opaque => { | ||||
|                             quote! { | ||||
|                                 #(#fold_member_name:)* self.#field_name | ||||
|                             } | ||||
|                         } | ||||
|                         ast::Field::Visible => { | ||||
|                             visit_members.push(quote! { | ||||
|                                 Visit::visit(&self.#field_name, state)?; | ||||
|                             }); | ||||
|                             quote! { | ||||
|                                 #(#fold_member_name:)* Fold::fold(self.#field_name, state)? | ||||
|                             } | ||||
|                         } | ||||
|                         ast::Field::RefVisible => { | ||||
|                             visit_members.push(quote! { | ||||
|                                 Visit::visit(self.#field_name, state)?; | ||||
|                             }); | ||||
|                             quote! { | ||||
|                                 #(#fold_member_name:)* Fold::fold(self.#field_name.clone(), state)? | ||||
|                             } | ||||
|                         } | ||||
|                     }; | ||||
|                     fold_members.push(fold_member); | ||||
|                 } | ||||
|                 let match_members = constructor | ||||
|                     .is_none() | ||||
|                     .then(|| { | ||||
|                         fields | ||||
|                             .keys() | ||||
|                             .map(|k| k.to_member()) | ||||
|                             .collect::<Option<Vec<_>>>() | ||||
|                             .map(|members| { | ||||
|                                 if members.is_empty() { | ||||
|                                     quote! { | ||||
|                                         let _ = state; | ||||
|                                         let Self {} = self; | ||||
|                                     } | ||||
|                                 } else { | ||||
|                                     quote! { | ||||
|                                         let Self { | ||||
|                                             #(#members: _,)* | ||||
|                                         } = self; | ||||
|                                     } | ||||
|                                 } | ||||
|                             }) | ||||
|                     }) | ||||
|                     .flatten(); | ||||
|                 visit_body = quote! { | ||||
|                     #match_members | ||||
|                     #(#visit_members)* | ||||
|                     Ok(()) | ||||
|                 }; | ||||
|                 let fold_body_tail = if let Some(constructor) = constructor { | ||||
|                     quote! { | ||||
|                         Ok(#constructor(#(#fold_members),*)) | ||||
|                     } | ||||
|                 } else { | ||||
|                     quote! { | ||||
|                         Ok(Self { | ||||
|                             #(#fold_members,)* | ||||
|                         }) | ||||
|                     } | ||||
|                 }; | ||||
|                 fold_body = quote! { | ||||
|                     #match_members | ||||
|                     #fold_body_tail | ||||
|                 }; | ||||
|             } | ||||
|             ast::Data::Enum(ast::Variants { variants }) => { | ||||
|                 let mut fold_arms = vec![]; | ||||
|                 let mut visit_arms = vec![]; | ||||
|                 let mut state_unused = true; | ||||
|                 for (variant_name, variant_field) in variants { | ||||
|                     let fold_arm; | ||||
|                     let visit_arm; | ||||
|                     match variant_field { | ||||
|                         Some(ast::Field::Visible) => { | ||||
|                             state_unused = false; | ||||
|                             fold_arm = quote! { | ||||
|                                 Self::#variant_name(v) => Fold::fold(v, state).map(Self::#variant_name), | ||||
|                             }; | ||||
|                             visit_arm = quote! { | ||||
|                                 Self::#variant_name(v) => Visit::visit(v, state), | ||||
|                             }; | ||||
|                         } | ||||
|                         Some(ast::Field::RefVisible) => { | ||||
|                             return Err(syn::Error::new( | ||||
|                                 Span::call_site(), | ||||
|                                 "enum variant field must not be RefVisible", | ||||
|                             )); | ||||
|                         } | ||||
|                         Some(ast::Field::Opaque) => { | ||||
|                             fold_arm = quote! { | ||||
|                                 Self::#variant_name(_) => Ok(self), | ||||
|                             }; | ||||
|                             visit_arm = quote! { | ||||
|                                 Self::#variant_name(_) => Ok(()), | ||||
|                             }; | ||||
|                         } | ||||
|                         None => { | ||||
|                             fold_arm = quote! { | ||||
|                                 Self::#variant_name => Ok(self), | ||||
|                             }; | ||||
|                             visit_arm = quote! { | ||||
|                                 Self::#variant_name => Ok(()), | ||||
|                             }; | ||||
|                         } | ||||
|                     } | ||||
|                     fold_arms.push(fold_arm); | ||||
|                     visit_arms.push(visit_arm); | ||||
|                 } | ||||
|                 let ignore_state = state_unused.then(|| { | ||||
|                     quote! { | ||||
|                         let _ = state; | ||||
|                     } | ||||
|                 }); | ||||
|                 visit_body = quote! { | ||||
|                     #ignore_state | ||||
|                     match self { | ||||
|                         #(#visit_arms)* | ||||
|                     } | ||||
|                 }; | ||||
|                 fold_body = quote! { | ||||
|                     #ignore_state | ||||
|                     match self { | ||||
|                         #(#fold_arms)* | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|         fold_impls.push(def_state.fold_impl(def_path, fold_body)); | ||||
|         visit_impls.push(def_state.visit_impl(def_path, visit_body)); | ||||
|     } | ||||
|     Ok(prettyplease::unparse(&parse_quote! { | ||||
|         pub trait Visitor { | ||||
|             type Error; | ||||
| 
 | ||||
|             #(#visitor_fns)* | ||||
|         } | ||||
| 
 | ||||
|         #(#visit_impls)* | ||||
| 
 | ||||
|         pub trait Folder { | ||||
|             type Error; | ||||
| 
 | ||||
|             #(#folder_fns)* | ||||
|         } | ||||
| 
 | ||||
|         #(#fold_impls)* | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| pub fn error_at_call_site<T: std::fmt::Display>(e: T) -> syn::Error { | ||||
|     syn::Error::new(Span::call_site(), e) | ||||
| } | ||||
| 
 | ||||
| pub fn parse_and_generate(path: impl AsRef<std::path::Path>) -> syn::Result<String> { | ||||
|     let input = fs::read_to_string(path).map_err(error_at_call_site)?; | ||||
|     let ast: ast::Definitions = serde_json::from_str(&input).map_err(error_at_call_site)?; | ||||
|     generate(&ast) | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue