ebml_iterable_specification_derive/lib.rs
1extern crate proc_macro;
2
3mod ast;
4mod attr;
5mod easy_ebml;
6mod pathing;
7
8use proc_macro::TokenStream;
9use syn::{ItemEnum, Error};
10use crate::easy_ebml::EasyEBML;
11
12///
13/// Attribute that derives implementations of [`EbmlSpecification`][spec] and [`EbmlTag`][tag] for an enum.
14///
15/// This macro is intended to make implementing the traits in ebml-iterable-specification easier to manage. Rather than requiring handwritten implementations for [`EbmlSpecification`][spec] and [`EbmlTag`][tag] methods, this macro understands attributes assigned to enum members and generates an implementation accordingly.
16///
17/// When deriving `EbmlSpecification` for an enum, the following attributes are required for each variant:
18/// * __#[id(`u64`)]__ - This attribute specifies the "id" of the tag. e.g. `0x1a45dfa3`
19/// * __#[data_type(`TagDataType`)]__ - This attribute specifies the type of data contained in the tag. e.g. `TagDataType::UnsignedInt`
20///
21/// The following attribute is optional for each variant:
22/// * __#[doc_path(Path/To/Element)]__ - This attribute specifies the document path of the current element. If this attribute is not present, the variant is treated as a Root element. Global elements can be defined with wildcard paths, e.g. #[doc_path(Segment/(1-)/)].
23///
24/// # Note
25///
26/// This attribute modifies the variants in the enumeration by adding fields to them. It also will add the following variants to the enum:
27/// - `Crc32(Vec<u8>)` - global tag defined in the EBML spec
28/// - `Void(Vec<u8>)` - global tag defined in the EBML spec
29/// - `RawTag(u64, Vec<u8>)` - used to support reading "unknown" tags that aren't in the spec
30///
31/// [spec]: ebml_iterable_specification::EbmlSpecification
32/// [tag]: ebml_iterable_specification::EbmlTag
33
34#[proc_macro_attribute]
35pub fn ebml_specification(_args: TokenStream, input: TokenStream) -> TokenStream {
36 let mut input = match syn::parse::<ItemEnum>(input) {
37 Ok(syntax_tree) => syntax_tree,
38 Err(err) => {
39 return TokenStream::from(Error::new(err.span(), "#[ebml_specification] attribute can only be applied to enums").to_compile_error())
40 },
41 };
42
43 attr::impl_ebml_specification(&mut input)
44 .unwrap_or_else(|err| err.to_compile_error())
45 .into()
46}
47
48///
49/// Macro that makes writing an EBML spec easy.
50///
51/// This provides an even easier alternative to create implementations of the [`EbmlSpecification`][spec] and [`EbmlTag`][tag] traits than using the [`[#ebml_specification]`][macro] attribute. As a bonus, your spec will be more legible and maintainable!
52///
53/// As an example, compare the following equivalent definitions:
54/// ```
55/// # use ebml_iterable_specification_derive::ebml_specification;
56/// # use ebml_iterable_specification::TagDataType::{Master, UnsignedInt};
57/// # pub mod ebml_iterable { pub mod specs {
58/// # pub use ebml_iterable_specification_derive::ebml_specification as ebml_specification;
59/// # pub use ebml_iterable_specification::EbmlSpecification as EbmlSpecification;
60/// # pub use ebml_iterable_specification::EbmlTag as EbmlTag;
61/// # pub use ebml_iterable_specification::TagDataType as TagDataType;
62/// # pub use ebml_iterable_specification::Master as Master;
63/// # pub use ebml_iterable_specification::PathPart as PathPart;
64/// # }}
65/// #[ebml_specification]
66/// #[derive(Clone)]
67/// enum Example {
68/// #[id(0x01)]
69/// #[data_type(Master)]
70/// Root,
71///
72/// #[id(0x02)]
73/// #[data_type(Master)]
74/// #[doc_path(Root)]
75/// Parent,
76///
77/// #[id(0x100)]
78/// #[data_type(UnsignedInt)]
79/// #[doc_path(Root/Parent)]
80/// Data,
81/// }
82/// ```
83/// vs
84/// ```
85/// # use ebml_iterable_specification_derive::easy_ebml;
86/// # use ebml_iterable_specification::TagDataType;
87/// # use ebml_iterable_specification::TagDataType::{Master, UnsignedInt};
88/// # pub mod ebml_iterable { pub mod specs {
89/// # pub use ebml_iterable_specification_derive::ebml_specification as ebml_specification;
90/// # pub use ebml_iterable_specification::EbmlSpecification as EbmlSpecification;
91/// # pub use ebml_iterable_specification::EbmlTag as EbmlTag;
92/// # pub use ebml_iterable_specification::TagDataType as TagDataType;
93/// # pub use ebml_iterable_specification::Master as Master;
94/// # pub use ebml_iterable_specification::PathPart as PathPart;
95/// # }}
96/// easy_ebml! {
97/// #[derive(Clone)]
98/// enum Example {
99/// Root : Master = 0x01,
100/// Root/Parent : Master = 0x02,
101/// Root/Parent/Data : UnsignedInt = 0x100,
102/// }
103/// }
104/// ```
105///
106/// Behind the scenes `easy_ebml!` still uses the existing [`[#ebml_specification]`][macro] attribute macro, so the final output of this macro will remain identical.
107///
108/// [spec]: ebml_iterable_specification::EbmlSpecification
109/// [tag]: ebml_iterable_specification::EbmlTag
110/// [macro]: macro@crate::ebml_specification
111
112#[proc_macro]
113pub fn easy_ebml(input: TokenStream) -> TokenStream {
114 let input = match syn::parse::<EasyEBML>(input) {
115 Ok(syntax_tree) => syntax_tree,
116 Err(err) => {
117 return TokenStream::from(Error::new(err.span(), "easy_ebml! {} content must be of format: enum Name {\
118 Root: Type = id,\
119 Path/Of/Component: Type = id,\
120 // example\
121 Ebml: Master = 0x1a45dfa3,\
122 Ebml/EbmlVersion: UnsignedInt = 0x4286,\
123 // global elements can be used in paths, example:\
124 (1-)/Crc32: Binary = 0xbf,\
125 }").to_compile_error())
126 },
127 };
128
129 input.implement().unwrap_or_else(|err| err.to_compile_error()).into()
130}