Skip to content

Commit 8c2f499

Browse files
committed
Make phf map macro take literal values rather than strings of Rust syntax
… in the style of https://github.com/dtolnay/proc-macro-hack/
1 parent 4890542 commit 8c2f499

File tree

5 files changed

+219
-192
lines changed

5 files changed

+219
-192
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
name = "cssparser"
4-
version = "0.11.0"
4+
version = "0.12.0"
55
authors = [ "Simon Sapin <simon.sapin@exyr.org>" ]
66

77
description = "Rust implementation of CSS Syntax Level 3"
@@ -20,7 +20,7 @@ tempdir = "0.3"
2020
encoding_rs = "0.5"
2121

2222
[dependencies]
23-
cssparser-macros = {path = "./macros", version = "0.1"}
23+
cssparser-macros = {path = "./macros", version = "0.2"}
2424
heapsize = {version = "0.3", optional = true}
2525
matches = "0.1"
2626
phf = "0.7"

macros/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cssparser-macros"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
55
description = "Procedural macros for cssparser"
66
documentation = "https://docs.rs/cssparser-macros/"
@@ -14,5 +14,4 @@ proc-macro = true
1414
[dependencies]
1515
phf_codegen = "0.7"
1616
quote = "0.3"
17-
syn = "0.11"
18-
17+
syn = {version = "0.11", features = ["full"]}

macros/lib.rs

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,31 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
4444
tokens.as_str().parse().unwrap()
4545
}
4646

47-
/// On `struct $Name($ValueType)`, add a new static method
48-
/// `fn map() -> &'static ::phf::Map<&'static str, $ValueType>`.
49-
/// The map’s content is given as:
50-
/// `#[cssparser__phf_map__kv_pairs(key = "…", value = "…", key = "…", value = "…")]`.
51-
/// Keys are ASCII-lowercased.
52-
#[proc_macro_derive(cssparser__phf_map,
53-
attributes(cssparser__phf_map__kv_pairs))]
47+
/// ```
48+
/// impl $Name {
49+
/// fn map() -> &'static ::phf::Map<&'static str, $ValueType> { … }
50+
/// }
51+
/// ```
52+
///
53+
/// Map keys are ASCII-lowercased.
54+
#[proc_macro_derive(cssparser__phf_map)]
5455
pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
5556
let input = syn::parse_macro_input(&input.to_string()).unwrap();
5657
let name = &input.ident;
57-
let value_type = match input.body {
58-
syn::Body::Struct(syn::VariantData::Tuple(ref fields)) if fields.len() == 1 => {
59-
&fields[0].ty
60-
}
61-
_ => panic!("expected tuple struct newtype, got {:?}", input.body)
62-
};
63-
64-
let pairs: Vec<_> = list_attr(&input, "cssparser__phf_map__kv_pairs").chunks(2).map(|chunk| {
65-
let key = sub_attr_value(&chunk[0], "key");
66-
let value = sub_attr_value(&chunk[1], "value");
67-
(key.to_ascii_lowercase(), value)
58+
let token_trees = find_smuggled_tokens(&input);
59+
let value_type = &token_trees[0];
60+
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
61+
let key = match chunk[0] {
62+
syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string,
63+
_ => panic!("expected string literal, got {:?}", chunk[0])
64+
};
65+
let value = &chunk[1];
66+
(key.to_ascii_lowercase(), quote!(#value).to_string())
6867
}).collect();
6968

7069
let mut map = phf_codegen::Map::new();
71-
for &(ref key, value) in &pairs {
72-
map.entry(&**key, value);
70+
for &(ref key, ref value) in &pairs {
71+
map.entry(&**key, &**value);
7372
}
7473

7574
let mut initializer_bytes = Vec::<u8>::new();
@@ -90,6 +89,42 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9089
tokens.as_str().parse().unwrap()
9190
}
9291

92+
/// Return the `…` part in:
93+
///
94+
/// ```rust
95+
/// enum $Name {
96+
/// Input = (0, stringify!(…)).0
97+
/// }
98+
/// ```
99+
fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] {
100+
let discriminant = match input.body {
101+
syn::Body::Enum(ref variants) if variants.len() == 1 => &variants[0].discriminant,
102+
_ => panic!("expected single-variant enum, got {:?}", input.body)
103+
};
104+
let tuple = match *discriminant {
105+
Some(syn::ConstExpr::Other(syn::Expr { node: syn::ExprKind::TupField(ref t, 0), .. })) => t,
106+
_ => panic!("expected a discriminant like tuple.0, got {:?}", discriminant)
107+
};
108+
let expr = match **tuple {
109+
syn::Expr { node: syn::ExprKind::Tup(ref values), .. } if values.len() == 2 => &values[1],
110+
_ => panic!("expected non-empty tuple, got {:?}", tuple)
111+
};
112+
let macro_args = match *expr {
113+
syn::Expr { node: syn::ExprKind::Mac(
114+
syn::Mac { ref tts, path: syn::Path { global: false, ref segments }}
115+
), .. }
116+
if segments.len() == 1
117+
&& segments[0] == syn::PathSegment::from("stringify")
118+
&& tts.len() == 1
119+
=> &tts[0],
120+
_ => panic!("expected a stringify!(…) macro, got {:?}", expr)
121+
};
122+
match *macro_args {
123+
syn::TokenTree::Delimited(syn::Delimited { ref tts, delim: syn::DelimToken::Paren }) => tts,
124+
_ => panic!("expected (…) parentheses, got {:?}", macro_args)
125+
}
126+
}
127+
93128
/// Panic if the first attribute isn’t `#[foo(…)]` with the given name,
94129
/// or return the parameters.
95130
fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] {

0 commit comments

Comments
 (0)