ilex_attr/lib.rs
1//! Implementation detail of `ilex`.
2
3use proc_macro::Delimiter;
4use proc_macro::Group;
5use proc_macro::Ident;
6use proc_macro::Punct;
7use proc_macro::Spacing;
8use proc_macro::Span;
9use proc_macro::TokenStream;
10use proc_macro::TokenTree;
11
12/// Generates a lexer spec struct.
13///
14/// This macro generates the type of struct described in the
15/// [crate documentation][crate]. The syntax is as follows.
16///
17/// ```ignore
18/// use ilex::rule::Keyword;
19/// use ilex::Lexeme;
20///
21/// /// My cool spec.
22/// #[ilex::spec]
23/// struct MySpec {
24/// #[named("...")]
25/// #[rule(/* ... */)]
26/// dollar: Lexeme<Keyword> = "$",
27/// }
28/// ```
29///
30/// The type of each field must be a [`Lexeme`] with a [`Rule`] type as its
31/// parameter. There are two special attributes that can follow.
32///
33/// - `#[named]` makes the rule into a *named* rule. This name can be used by
34/// diagnostics, and corresponds to calling `Spec::named_rule()`.
35///
36/// - `#[rule]` is the value to use to construct the rule, which must be
37/// `Into<R>`, where `R` is the type inside `Lexeme` (so, above, the rule
38/// value must be `Into<Keyword>`). By default, this value is the name of the
39/// rule, to make the common case of declaring a keyword as simple as writing
40/// `nullptr: Lexeme<Keyword>`, assuming Rust itself doesn't already use that
41/// keyword.
42///
43/// Note that *order matters* for the fields: when breaking a tie between two
44/// potential tokens of the same length, the first one in the struct will win.
45/// In practice, this means you should put keywords before identifiers.
46///
47/// Additionally, the following functions will be defined for the `MySpec` type.
48///
49/// ```
50/// # struct Spec;
51/// # struct MySpec;
52/// # fn norun(_: i32) {
53/// impl MySpec {
54/// /// Gets the global instance of this spec.
55/// pub fn get() -> &'static Self {
56/// // ...
57/// # todo!()
58/// }
59///
60/// /// Gets the actual compiled spec.
61/// pub fn spec(&self) -> &Spec {
62/// // ...
63/// # todo!()
64/// }
65/// }
66/// # }
67/// ```
68///
69// God cross-trait links suck.
70/// [`Lexeme`]: https://docs.rs/ilex/latest/ilex/struct.Lexeme.html
71/// [`Rule`]: https://docs.rs/ilex/latest/ilex/rule/trait.Rule.html
72/// [crate]: https://docs.rs/ilex
73#[proc_macro_attribute]
74pub fn spec(_attr: TokenStream, item: TokenStream) -> TokenStream {
75 // This is implemented as a decl macro, because that's easier to
76 // understand and debug than proc macros. I hate proc macros so much.
77 let span = Span::call_site();
78 let macro_call: [TokenTree; 8] = [
79 Punct::new(':', Spacing::Joint).into(),
80 Punct::new(':', Spacing::Alone).into(),
81 Ident::new("ilex", span).into(),
82 Punct::new(':', Spacing::Joint).into(),
83 Punct::new(':', Spacing::Alone).into(),
84 Ident::new("__spec__", span).into(),
85 Punct::new('!', Spacing::Alone).into(),
86 Group::new(Delimiter::Brace, item).into(),
87 ];
88
89 macro_call.into_iter().collect()
90}
91
92// This helper exists only to make the #[spec] field attributes inert.
93#[doc(hidden)]
94#[proc_macro_derive(derive_hack, attributes(named, rule))]
95pub fn derive(_: TokenStream) -> TokenStream {
96 TokenStream::new()
97}