forked from libre-chip/fayalite
initial public commit
This commit is contained in:
commit
0b958e7852
56 changed files with 30235 additions and 0 deletions
18
crates/fayalite-visit-gen/Cargo.toml
Normal file
18
crates/fayalite-visit-gen/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
# See Notices.txt for copyright information
|
||||
[package]
|
||||
name = "fayalite-visit-gen"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
workspace = "../.."
|
||||
license = "LGPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||
prettyplease = "0.2.20"
|
||||
proc-macro2 = "1.0.83"
|
||||
quote = "1.0.36"
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
||||
syn = { version = "2.0.66", features = ["full", "extra-traits"] }
|
||||
thiserror = "1.0.61"
|
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