1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use heck::ToShoutySnakeCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse2, parse_quote, Attribute, DeriveInput, Error, Expr, LitInt, LitStr, Result};

use crate::add_trait_bounds;

pub(super) fn derive_packet(item: TokenStream) -> Result<TokenStream> {
    let mut input = parse2::<DeriveInput>(item)?;

    let packet_attr = parse_packet_helper_attr(&input.attrs)?.unwrap_or_default();

    let name = input.ident.clone();

    let name_str = if let Some(attr_name) = packet_attr.name {
        attr_name.value()
    } else {
        name.to_string()
    };

    let packet_id: Expr = match packet_attr.id {
        Some(expr) => expr,
        None => match syn::parse_str::<Ident>(&name_str.to_shouty_snake_case()) {
            Ok(ident) => parse_quote!(::valence_protocol::packet_id::#ident),
            Err(_) => {
                return Err(Error::new(
                    packet_attr.span,
                    "missing valid `id = ...` value from `packet` attr",
                ))
            }
        },
    };

    add_trait_bounds(&mut input.generics, quote!(::std::fmt::Debug));

    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    let side = if let Some(side_attr) = packet_attr.side {
        side_attr
    } else if name_str.to_lowercase().ends_with("s2c") {
        parse_quote!(::valence_protocol::PacketSide::Clientbound)
    } else if name_str.to_lowercase().ends_with("c2s") {
        parse_quote!(::valence_protocol::PacketSide::Serverbound)
    } else {
        return Err(Error::new(
            packet_attr.span,
            "missing `side = PacketSide::...` value from `packet` attribute",
        ));
    };

    let state = packet_attr
        .state
        .unwrap_or_else(|| parse_quote!(::valence_protocol::PacketState::Play));

    Ok(quote! {
        impl #impl_generics ::valence_protocol::__private::Packet for #name #ty_generics
        #where_clause
        {
            const ID: i32 = #packet_id;
            const NAME: &'static str = #name_str;
            const SIDE: ::valence_protocol::PacketSide = #side;
            const STATE: ::valence_protocol::PacketState = #state;
        }
    })
}

struct PacketAttr {
    span: Span,
    id: Option<Expr>,
    tag: Option<i32>,
    name: Option<LitStr>,
    side: Option<Expr>,
    state: Option<Expr>,
}

impl Default for PacketAttr {
    fn default() -> Self {
        Self {
            span: Span::call_site(),
            id: Default::default(),
            tag: Default::default(),
            name: Default::default(),
            side: Default::default(),
            state: Default::default(),
        }
    }
}

fn parse_packet_helper_attr(attrs: &[Attribute]) -> Result<Option<PacketAttr>> {
    for attr in attrs {
        if attr.path().is_ident("packet") {
            let mut res = PacketAttr {
                span: attr.span(),
                id: None,
                tag: None,
                name: None,
                side: None,
                state: None,
            };

            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("id") {
                    res.id = Some(meta.value()?.parse::<Expr>()?);
                    Ok(())
                } else if meta.path.is_ident("tag") {
                    res.tag = Some(meta.value()?.parse::<LitInt>()?.base10_parse::<i32>()?);
                    Ok(())
                } else if meta.path.is_ident("name") {
                    res.name = Some(meta.value()?.parse::<LitStr>()?);
                    Ok(())
                } else if meta.path.is_ident("side") {
                    res.side = Some(meta.value()?.parse::<Expr>()?);
                    Ok(())
                } else if meta.path.is_ident("state") {
                    res.state = Some(meta.value()?.parse::<Expr>()?);
                    Ok(())
                } else {
                    Err(meta.error("unrecognized packet argument"))
                }
            })?;

            return Ok(Some(res));
        }
    }

    Ok(None)
}