Skip to content

Commit

Permalink
Refactor the entire spec into a single file, add a bunch of WIP highl…
Browse files Browse the repository at this point in the history
…evel APIs
  • Loading branch information
necauqua committed Jul 26, 2023
1 parent cc7204c commit 37ba128
Show file tree
Hide file tree
Showing 51 changed files with 5,600 additions and 3,780 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ rust-version = '1.66.1'
[dependencies]
jdwp-macros = { path = 'jdwp-macros' }
paste = '1.0'
thiserror = '1.0'

log = '0.4'
byteorder = '1.4'
bitflags = '2.3'
thiserror = '1.0'
cesu8 = '1.1'

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion jdwp-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ edition = '2018'
proc-macro = true

[dependencies]
syn = { version = '1.0', features = ['full'] }
syn = { version = '2.0', features = ['full', 'extra-traits'] }
quote = '1.0'
proc-macro2 = '1.0'
169 changes: 117 additions & 52 deletions jdwp-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use proc_macro::TokenStream;

use proc_macro2::Ident;
Expand All @@ -7,7 +9,7 @@ use syn::{
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Data, Error, Fields, GenericParam, Index, LitInt, Token, Type,
Attribute, Data, Error, Fields, GenericParam, Index, LitInt, Meta, Token, Type, TypePath,
};

fn get_generic_names(generic_params: &Punctuated<GenericParam, Comma>) -> proc_macro2::TokenStream {
Expand All @@ -21,33 +23,77 @@ fn get_generic_names(generic_params: &Punctuated<GenericParam, Comma>) -> proc_m
quote!(#(#generics,)*)
}

fn is_attr_ident(attr: &Attribute, name: &str) -> bool {
match &attr.meta {
Meta::List(list) => list.path.is_ident(name),
Meta::NameValue(name_value) => name_value.path.is_ident(name),
_ => false,
}
}

fn is_generic_param(
generic_params: &Punctuated<GenericParam, Comma>,
ty: &Type,
) -> Option<TypePath> {
if let Type::Path(path) = ty {
if let Some(prefix) = path.path.segments.first() {
return generic_params
.iter()
.any(|param| match param {
GenericParam::Type(type_param) => type_param.ident == prefix.ident,
_ => false,
})
.then(|| path.clone());
}
}
None
}

#[proc_macro_derive(JdwpReadable, attributes(skip))]
pub fn jdwp_readable(item: TokenStream) -> TokenStream {
let derive_input = syn::parse_macro_input!(item as syn::DeriveInput);

match &derive_input.data {
Data::Struct(struct_data) => {
let ident = derive_input.ident;
let generic_params = derive_input.generics.params;
let generic_names = get_generic_names(&generic_params);
let generics_where = derive_input.generics.where_clause;
let mut field_types = HashSet::new();
let read = match &struct_data.fields {
Fields::Unit => quote!(Ok(Self)),
Fields::Named(named) => {
let fields = named.named.iter().map(|f| {
let name = f.ident.as_ref().unwrap(); // we are in Named branch so this is not None
if let Some(path) = is_generic_param(&generic_params, &f.ty) {
field_types.insert(path);
}
quote!(#name: ::jdwp::codec::JdwpReadable::read(read)?)
});
quote!(Ok(Self { #(#fields),* }))
}
Fields::Unnamed(unnamed) => {
let fields = (0..unnamed.unnamed.len())
.map(|_| quote!(::jdwp::codec::JdwpReadable::read(read)?));
let fields = unnamed.unnamed.iter().map(|f| {
if let Some(path) = is_generic_param(&generic_params, &f.ty) {
field_types.insert(path);
}
quote!(::jdwp::codec::JdwpReadable::read(read)?)
});
quote!(Ok(Self(#(#fields),*)))
}
};

let mut generic_predicates = quote!();
if let Some(existing) = derive_input.generics.where_clause {
for predicate in existing.predicates {
generic_predicates.extend(quote!(#predicate,));
}
}
for field_type in field_types {
generic_predicates.extend(quote!(#field_type: ::jdwp::codec::JdwpReadable,));
}

let ident = derive_input.ident;
let generic_names = get_generic_names(&generic_params);
let tokens = quote! {
impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> #generics_where {
impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> where #generic_predicates {
fn read<R: ::std::io::Read>(read: &mut ::jdwp::codec::JdwpReader<R>) -> ::std::io::Result<Self> {
#read
}
Expand All @@ -59,44 +105,64 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream {
let Some(repr) = derive_input
.attrs
.iter()
.find(|attr| attr.path.is_ident("repr")) else {
.find(|attr| is_attr_ident(attr, "repr")) else {
return Error::new(enum_data.enum_token.span, "No explicit repr")
.to_compile_error()
.into();
};
let repr = repr.parse_args::<Type>().expect("TODO");
let repr = repr.parse_args::<Type>().expect("Bad repr"); // todo better error

let mut match_arms = Vec::with_capacity(enum_data.variants.len());
let mut field_types = HashSet::new();

let generic_params = derive_input.generics.params;

for v in &enum_data.variants {
let Some((_, ref d)) = v.discriminant else {
return Error::new(
v.span(),
"No explicit discriminant",
)
.to_compile_error()
.into()
return Error::new(v.span(), "No explicit discriminant")
.to_compile_error()
.into()
};
let name = &v.ident;

let constructor = match &v.fields {
Fields::Named(named) => {
let fields = named.named.iter().map(|f| f.ident.as_ref().unwrap());
let fields = named.named.iter().map(|f| {
if let Some(path) = is_generic_param(&generic_params, &f.ty) {
field_types.insert(path);
}
f.ident.as_ref().unwrap()
});
quote!( { #(#fields: ::jdwp::codec::JdwpReadable::read(read)?,)* } )
}
Fields::Unnamed(unnamed) => {
let fields = unnamed
.unnamed
.iter()
.map(|_| quote!(::jdwp::codec::JdwpReadable::read(read)?));
let fields = unnamed.unnamed.iter().map(|f| {
if let Some(path) = is_generic_param(&generic_params, &f.ty) {
field_types.insert(path);
}
quote!(::jdwp::codec::JdwpReadable::read(read)?)
});
quote!( ( #(#fields),* ) )
}
Fields::Unit => quote!(),
};
match_arms.push(quote!(x if x == (#d) => Self::#name #constructor));
}
let ident = &derive_input.ident;

let mut generic_predicates = quote!();
if let Some(existing) = derive_input.generics.where_clause {
for predicate in existing.predicates {
generic_predicates.extend(quote!(#predicate,));
}
}
for field_type in field_types {
generic_predicates.extend(quote!(#field_type: ::jdwp::codec::JdwpReadable,));
}

let ident = derive_input.ident;
let generic_names = get_generic_names(&generic_params);
let tokens = quote! {
impl ::jdwp::codec::JdwpReadable for #ident {
impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> where #generic_predicates {
fn read<R: ::std::io::Read>(read: &mut ::jdwp::codec::JdwpReader<R>) -> ::std::io::Result<Self> {
let res = match #repr::read(read)? {
#(#match_arms,)*
Expand Down Expand Up @@ -158,7 +224,7 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream {
let Some(repr) = derive_input
.attrs
.iter()
.find(|attr| attr.path.is_ident("repr")) else {
.find(|attr| is_attr_ident(attr, "repr")) else {
return Error::new(enum_data.enum_token.span, "No explicit repr")
.to_compile_error()
.into();
Expand Down Expand Up @@ -205,8 +271,11 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream {
});
}
let ident = derive_input.ident;
let generic_params = derive_input.generics.params;
let generic_names = get_generic_names(&generic_params);
let generics_where = derive_input.generics.where_clause;
let tokens = quote! {
impl ::jdwp::codec::JdwpWritable for #ident {
impl<#generic_params> ::jdwp::codec::JdwpWritable for #ident<#generic_names> #generics_where {
fn write<W: ::std::io::Write>(&self, write: &mut ::jdwp::codec::JdwpWriter<W>) -> ::std::io::Result<()> {
match self {
#(#match_arms)*
Expand Down Expand Up @@ -234,7 +303,7 @@ struct CommandAttr {
impl Parse for CommandAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let reply_type = input.parse()?;
let _ = input.parse::<Token![,]>()?;
_ = input.parse::<Token![,]>()?;
Ok(CommandAttr {
reply_type,
command: input.parse()?,
Expand All @@ -259,7 +328,7 @@ impl ShortCommandAttr {
impl Parse for ShortCommandAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let command_set = input.parse()?;
let _ = input.parse::<Token![,]>()?;
_ = input.parse::<Token![,]>()?;
Ok(ShortCommandAttr {
command_set,
command_id: input.parse()?,
Expand Down Expand Up @@ -291,9 +360,7 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream {
let generic_names = get_generic_names(generic_params);
let generics_where = &item.generics.where_clause;

let new = if item.fields.is_empty() {
quote!()
} else {
let new = if !item.fields.is_empty() {
let mut docs = Vec::with_capacity(item.fields.len());
let mut typed_idents = Vec::with_capacity(item.fields.len());
let mut idents = Vec::with_capacity(item.fields.len());
Expand All @@ -302,32 +369,28 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream {
Some(ref ident) => {
let ty = &f.ty;

// this is very cringe but also very simple
let stype = quote!(#ty).to_string();
let string_magic = stype == "String";
let phantom = stype.starts_with("PhantomData ");
// this is pretty cringe but eh
let phantom = quote!(#ty).to_string().starts_with("PhantomData ");

if !phantom {
typed_idents.push(if string_magic {
quote!(#ident: impl Into<String>)
} else {
quote!(#ident: #ty)
});
typed_idents.push(quote!(#ident: #ty));
}

docs.push(f.attrs.iter().find(|a| a.path.is_ident("doc")).map(|a| {
let tokens = &a.tokens;
quote! {
#[doc = stringify!(#ident)]
#[doc = " - "]
#[doc #tokens]
#[doc = "\n"]
}
}));
docs.push(
f.attrs
.iter()
.find(|attr| is_attr_ident(attr, "doc"))
.map(|attr| {
quote! {
#[doc = stringify!(#ident)]
#[doc = " - "]
#attr
#[doc = "\n"]
}
}),
);

idents.push(if string_magic {
quote!(#ident: #ident.into())
} else if phantom {
idents.push(if phantom {
quote!(#ident: ::std::marker::PhantomData)
} else {
quote!(#ident)
Expand All @@ -350,14 +413,16 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream {
}
}
}
} else {
quote!()
};

let tokens = quote! {
#item
#new

impl<#generic_params> ::jdwp::commands::Command for #ident<#generic_names> #generics_where {
const ID: ::jdwp::CommandId = ::jdwp::CommandId::new(#command_set, #command_id);
impl<#generic_params> ::jdwp::spec::Command for #ident<#generic_names> #generics_where {
const ID: ::jdwp::spec::CommandId = ::jdwp::spec::CommandId::new(#command_set, #command_id);
type Output = #reply_type;
}
};
Expand Down
Loading

0 comments on commit 37ba128

Please sign in to comment.