Skip to content

Commit 5efba23

Browse files
committed
Move proc-macro trickery to a separate crate.
It could be useful outside of cssparser.
1 parent 0909afd commit 5efba23

File tree

7 files changed

+102
-101
lines changed

7 files changed

+102
-101
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ cssparser-macros = {path = "./macros", version = "0.2"}
2424
heapsize = {version = "0.3", optional = true}
2525
matches = "0.1"
2626
phf = "0.7"
27+
procedural-masquarade = {path = "./procedural-masquarade", version = "0.1"}
2728
serde = {version = "0.9", optional = true}
2829

2930
[build-dependencies]
@@ -35,4 +36,4 @@ bench = []
3536
dummy_match_byte = []
3637

3738
[workspace]
38-
members = [".", "./macros"]
39+
members = [".", "./macros", "./procedural-masquarade"]

macros/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ path = "lib.rs"
1212
proc-macro = true
1313

1414
[dependencies]
15+
procedural-masquarade = {path = "../procedural-masquarade", version = "0.1"}
1516
phf_codegen = "0.7"
16-
quote = "0.3"
17-
syn = {version = "0.11", features = ["full"]}
17+
quote = "0.3.14"
18+
syn = {version = "0.11.8", features = ["full"]}

macros/lib.rs

Lines changed: 28 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5+
#[macro_use] extern crate procedural_masquarade;
56
extern crate phf_codegen;
67
extern crate proc_macro;
78
#[macro_use] extern crate quote;
89
extern crate syn;
910

1011
use std::ascii::AsciiExt;
1112

12-
/// Panic if any string contains ASCII uppercase letters.
13-
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
14-
#[proc_macro_derive(cssparser_internal__assert_ascii_lowercase__max_len)]
15-
pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
16-
max_len_common(input, |token_trees| {
17-
let tokens = quote!( match x { #( #token_trees )* } );
18-
let expr = syn::parse_expr(tokens.as_str()).unwrap();
13+
define_proc_macros! {
14+
/// Panic if any string contains ASCII uppercase letters.
15+
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
16+
#[allow(non_snake_case)]
17+
pub fn cssparser_internal__assert_ascii_lowercase__max_len(input: &str) -> String {
18+
let expr = syn::parse_expr(&format!("match x {{ {} }}", input)).unwrap();
1919
let arms = match expr {
2020
syn::Expr { node: syn::ExprKind::Match(_, ref arms), .. } => arms,
2121
_ => panic!("expected a match expression, got {:?}", expr)
2222
};
23-
arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| {
23+
max_len(arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| {
2424
let expr = match *pattern {
2525
syn::Pat::Lit(ref expr) => expr,
2626
syn::Pat::Wild |
@@ -42,36 +42,23 @@ pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_ma
4242
}
4343
_ => panic!("expected string pattern, got {:?}", expr)
4444
}
45-
}).max()
46-
})
47-
}
48-
49-
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
50-
#[proc_macro_derive(cssparser_internal__max_len)]
51-
pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
52-
max_len_common(input, |token_trees| {
53-
token_trees.iter().map(|tt| string_literal(tt).len()).max()
54-
})
55-
}
45+
}))
46+
}
5647

57-
fn max_len_common<F>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
58-
where F : FnOnce(&[syn::TokenTree]) -> Option<usize> {
59-
common(input, |token_trees| {
60-
let max_length = f(token_trees).expect("expected at least one string");
61-
quote! {
62-
const MAX_LENGTH: usize = #max_length;
63-
}
64-
})
65-
}
48+
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
49+
#[allow(non_snake_case)]
50+
pub fn cssparser_internal__max_len(input: &str) -> String {
51+
max_len(syn::parse_token_trees(input).unwrap().iter().map(|tt| string_literal(tt).len()))
52+
}
6653

67-
/// ```
68-
/// static MAP: &'static ::phf::Map<&'static str, $ValueType> = …;
69-
/// ```
70-
///
71-
/// Map keys are ASCII-lowercased.
72-
#[proc_macro_derive(cssparser_internal__phf_map)]
73-
pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
74-
common(input, |token_trees| {
54+
/// ```
55+
/// static MAP: &'static ::phf::Map<&'static str, $ValueType> = …;
56+
/// ```
57+
///
58+
/// Map keys are ASCII-lowercased.
59+
#[allow(non_snake_case)]
60+
pub fn cssparser_internal__phf_map(input: &str) -> String {
61+
let token_trees = syn::parse_token_trees(input).unwrap();
7562
let value_type = &token_trees[0];
7663
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
7764
let key = string_literal(&chunk[0]);
@@ -91,52 +78,13 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9178
map.build(&mut initializer_bytes).unwrap();
9279
tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());
9380
tokens.append(";");
94-
tokens
95-
})
96-
}
97-
98-
fn common<F>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
99-
where F: FnOnce(&[syn::TokenTree]) -> quote::Tokens {
100-
let input = syn::parse_macro_input(&input.to_string()).unwrap();
101-
let token_trees = find_smuggled_tokens(&input);
102-
let tokens = f(token_trees);
103-
tokens.as_str().parse().unwrap()
81+
tokens.to_string()
82+
}
10483
}
10584

106-
/// Return the `…` part in:
107-
///
108-
/// ```rust
109-
/// enum Somthing {
110-
/// Input = (0, stringify!(…)).0
111-
/// }
112-
/// ```
113-
fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] {
114-
let discriminant = match input.body {
115-
syn::Body::Enum(ref variants) if variants.len() == 1 => &variants[0].discriminant,
116-
_ => panic!("expected single-variant enum, got {:?}", input.body)
117-
};
118-
let tuple = match *discriminant {
119-
Some(syn::ConstExpr::Other(syn::Expr { node: syn::ExprKind::TupField(ref t, 0), .. })) => t,
120-
_ => panic!("expected a discriminant like tuple.0, got {:?}", discriminant)
121-
};
122-
let expr = match **tuple {
123-
syn::Expr { node: syn::ExprKind::Tup(ref values), .. } if values.len() == 2 => &values[1],
124-
_ => panic!("expected non-empty tuple, got {:?}", tuple)
125-
};
126-
let macro_args = match *expr {
127-
syn::Expr { node: syn::ExprKind::Mac(
128-
syn::Mac { ref tts, path: syn::Path { global: false, ref segments }}
129-
), .. }
130-
if segments.len() == 1
131-
&& segments[0] == syn::PathSegment::from("stringify")
132-
&& tts.len() == 1
133-
=> &tts[0],
134-
_ => panic!("expected a stringify!(…) macro, got {:?}", expr)
135-
};
136-
match *macro_args {
137-
syn::TokenTree::Delimited(syn::Delimited { ref tts, delim: syn::DelimToken::Paren }) => tts,
138-
_ => panic!("expected (…) parentheses, got {:?}", macro_args)
139-
}
85+
fn max_len<I: Iterator<Item=usize>>(lengths: I) -> String {
86+
let max_length = lengths.max().expect("expected at least one string");
87+
quote!( const MAX_LENGTH: usize = #max_length; ).to_string()
14088
}
14189

14290
fn string_literal(token: &syn::TokenTree) -> &str {

procedural-masquarade/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "procedural-masquarade"
3+
version = "0.1.0"
4+
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
5+
description = "macro_rules for making proc_macro_derive pretending to be proc_macro"
6+
documentation = "https://docs.rs/procedural-masquarade/"
7+
repository = "https://github.com/servo/rust-cssparser"
8+
license = "MIT/Apache-2.0"
9+
10+
[lib]
11+
path = "lib.rs"

procedural-masquarade/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#[macro_export]
2+
macro_rules! define_proc_macros {
3+
(
4+
$(
5+
$( #[$attr:meta] )*
6+
pub fn $func:ident($input:ident: &str) -> String
7+
$body:block
8+
)+
9+
) => {
10+
$(
11+
$( #[$attr] )*
12+
#[proc_macro_derive($func)]
13+
pub fn $func(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
14+
// Use another function to hide this one’s local variables from $block
15+
#[inline]
16+
fn implementation($input: &str) -> String $body
17+
18+
let input = input.to_string();
19+
let mut normalized = String::with_capacity(input.len());
20+
for piece in input.split_whitespace() {
21+
normalized.push_str(piece);
22+
normalized.push(' ');
23+
}
24+
25+
let prefix = "#[allow(unused)] enum Dummy { Input = (0, stringify!(";
26+
let suffix = ")).0, } ";
27+
assert!(normalized.starts_with(prefix), "Unexpected proc_macro_derive input {:?}", input);
28+
assert!(normalized.ends_with(suffix), "Unexpected proc_macro_derive input {:?}", input);
29+
30+
let start = prefix.len();
31+
let end = normalized.len() - suffix.len();
32+
let output = implementation(&normalized[start..end]);
33+
output.parse().unwrap()
34+
}
35+
)+
36+
}
37+
}
38+
39+
#[macro_export]
40+
macro_rules! define_invoke_proc_macro {
41+
($macro_name: ident) => {
42+
#[macro_export]
43+
macro_rules! $macro_name {
44+
($proc_macro_name: ident ! $paren: tt) => {
45+
#[derive($proc_macro_name)]
46+
#[allow(unused)]
47+
enum Dummy {
48+
Input = (0, stringify! $paren ).0
49+
}
50+
}
51+
}
52+
}
53+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser)
7070

7171
#[macro_use] extern crate cssparser_macros;
7272
#[macro_use] extern crate matches;
73+
#[macro_use] extern crate procedural_masquarade;
7374
extern crate phf;
7475
#[cfg(test)] extern crate encoding_rs;
7576
#[cfg(test)] extern crate tempdir;

src/macros.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5+
define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro);
56

67
/// Expands to an expression equivalent to a `match` with string patterns,
78
/// but matching is case-insensitive in the ASCII range.
@@ -37,7 +38,7 @@
3738
macro_rules! match_ignore_ascii_case {
3839
( $input:expr, $( $match_body:tt )* ) => {
3940
{
40-
cssparser_internal__pretend_proc_macro! {
41+
cssparser_internal__invoke_proc_macro! {
4142
cssparser_internal__assert_ascii_lowercase__max_len!( $( $match_body )* )
4243
}
4344

@@ -86,12 +87,12 @@ macro_rules! match_ignore_ascii_case {
8687
macro_rules! ascii_case_insensitive_phf_map {
8788
($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr, )* }) => {
8889
fn $name(input: &str) -> Option<&'static $ValueType> {
89-
cssparser_internal__pretend_proc_macro! {
90+
cssparser_internal__invoke_proc_macro! {
9091
cssparser_internal__phf_map!( ($ValueType) $( $key ($value) )+ )
9192
}
9293

9394
{
94-
cssparser_internal__pretend_proc_macro! {
95+
cssparser_internal__invoke_proc_macro! {
9596
cssparser_internal__max_len!( $( $key )+ )
9697
}
9798
// MAX_LENGTH is generated by cssparser_internal__max_len
@@ -125,21 +126,6 @@ macro_rules! cssparser_internal__to_lowercase {
125126
}
126127
}
127128

128-
/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros.
129-
///
130-
/// **This macro is not part of the public API. It can change or be removed between any versions.**
131-
#[macro_export]
132-
#[doc(hidden)]
133-
macro_rules! cssparser_internal__pretend_proc_macro {
134-
($macro_name: ident ! ( $($tt: tt)* )) => {
135-
#[derive($macro_name)]
136-
#[allow(unused)]
137-
enum Dummy {
138-
Input = (0, stringify!( $( $tt )* )).0
139-
}
140-
}
141-
}
142-
143129
/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros.
144130
///
145131
/// **This function is not part of the public API. It can change or be removed between any verisons.**

0 commit comments

Comments
 (0)