// 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 = ::Err; fn try_from(v: &str) -> Result { v.parse() } } impl TryFrom for $ty { type Error = ::Err; fn try_from(v: String) -> Result { 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 for Path { fn from(value: Ident) -> Self { Path(value.0) } } impl From for String { fn from(value: Ident) -> Self { value.0 } } impl From 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 { 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 { 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.next_back() } } impl FusedIterator for PathIter<'_> {} impl DoubleEndedIterator for PathIter<'_> { fn next_back(&mut self) -> Option { 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(), ::default()); } } #[derive(Clone, Debug, Error)] #[error("invalid path")] pub struct PathParseError; impl From 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 { 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, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Definition { #[serde(default, skip_serializing_if = "Option::is_none")] pub fn_name_suffix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub generics: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub fold_where: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub visit_where: Option, 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 { 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 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 { 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); #[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 { Ok(Self( syn::parse::Parser::parse_str(syn::punctuated::Punctuated::parse_terminated, value) .map_err(|_| WherePredicatesParseError)?, )) } } impl From for String { fn from(value: WherePredicates) -> Self { value.0.into_token_stream().to_string() } } impl From for syn::WhereClause { fn from(value: WherePredicates) -> Self { syn::WhereClause { where_token: Default::default(), predicates: value.0, } } } impl From 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 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 for Generics { type Error = GenericsParseError; fn try_from(value: SerializedGenerics) -> Result { 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::(&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, } 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 { ::default().to_tokens(tokens); generics.to_tokens(tokens); } } } impl From 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 { 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 { 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 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 { 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, #[serde(flatten)] pub fields: IndexMap, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct Variants { pub variants: IndexMap>, } #[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: "".into(), where_predicates: "T::Type: BundleType," .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" } } } }"# ); } }