Skip to content

Commit 4890542

Browse files
authored
Merge pull request #122 from servo/macros
Make match_ignore_ascii_case more efficient, add ascii_case_insensitive_phf_map
2 parents f9282a0 + 138ccaf commit 4890542

File tree

6 files changed

+472
-188
lines changed

6 files changed

+472
-188
lines changed

Cargo.toml

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[package]
22

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

77
description = "Rust implementation of CSS Syntax Level 3"
8-
documentation = "http://servo.github.io/rust-cssparser/cssparser/index.html"
8+
documentation = "https://docs.rs/cssparser/"
99
repository = "https://github.com/servo/rust-cssparser"
1010
readme = "README.md"
1111
keywords = ["css", "syntax", "parser"]
@@ -14,23 +14,25 @@ build = "build.rs"
1414

1515
exclude = ["src/css-parsing-tests"]
1616

17-
[lib]
18-
doctest = false
19-
2017
[dev-dependencies]
2118
rustc-serialize = "0.3"
2219
tempdir = "0.3"
23-
encoding_rs = "0.3.2"
20+
encoding_rs = "0.5"
2421

2522
[dependencies]
23+
cssparser-macros = {path = "./macros", version = "0.1"}
2624
heapsize = {version = "0.3", optional = true}
2725
matches = "0.1"
26+
phf = "0.7"
2827
serde = {version = "0.9", optional = true}
2928

3029
[build-dependencies]
31-
syn = { version = "0.10.6", features = ["full", "visit"]}
30+
syn = "0.11"
3231
quote = "0.3"
3332

3433
[features]
3534
bench = []
3635
dummy_match_byte = []
36+
37+
[workspace]
38+
members = [".", "./macros"]

macros/Cargo.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "cssparser-macros"
3+
version = "0.1.0"
4+
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
5+
description = "Procedural macros for cssparser"
6+
documentation = "https://docs.rs/cssparser-macros/"
7+
repository = "https://github.com/servo/rust-cssparser"
8+
license = "MPL-2.0"
9+
10+
[lib]
11+
path = "lib.rs"
12+
proc-macro = true
13+
14+
[dependencies]
15+
phf_codegen = "0.7"
16+
quote = "0.3"
17+
syn = "0.11"
18+

macros/lib.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
extern crate phf_codegen;
6+
extern crate proc_macro;
7+
#[macro_use] extern crate quote;
8+
extern crate syn;
9+
10+
use std::ascii::AsciiExt;
11+
12+
/// Find a `#[cssparser__assert_ascii_lowercase__data(string = "…", string = "…")]` attribute,
13+
/// and panic if any string contains ASCII uppercase letters.
14+
#[proc_macro_derive(cssparser__assert_ascii_lowercase,
15+
attributes(cssparser__assert_ascii_lowercase__data))]
16+
pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
17+
let input = syn::parse_macro_input(&input.to_string()).unwrap();
18+
let data = list_attr(&input, "cssparser__assert_ascii_lowercase__data");
19+
20+
for sub_attr in data {
21+
let string = sub_attr_value(sub_attr, "string");
22+
assert_eq!(*string, string.to_ascii_lowercase(),
23+
"the expected strings must be given in ASCII lowercase");
24+
}
25+
26+
"".parse().unwrap()
27+
}
28+
29+
/// Find a `#[cssparser__max_len__data(string = "…", string = "…")]` attribute,
30+
/// and emit a `MAX_LENGTH` constant with the length of the longest string.
31+
#[proc_macro_derive(cssparser__max_len,
32+
attributes(cssparser__max_len__data))]
33+
pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34+
let input = syn::parse_macro_input(&input.to_string()).unwrap();
35+
let data = list_attr(&input, "cssparser__max_len__data");
36+
37+
let lengths = data.iter().map(|sub_attr| sub_attr_value(sub_attr, "string").len());
38+
let max_length = lengths.max().expect("expected at least one string");
39+
40+
let tokens = quote! {
41+
const MAX_LENGTH: usize = #max_length;
42+
};
43+
44+
tokens.as_str().parse().unwrap()
45+
}
46+
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))]
54+
pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
55+
let input = syn::parse_macro_input(&input.to_string()).unwrap();
56+
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)
68+
}).collect();
69+
70+
let mut map = phf_codegen::Map::new();
71+
for &(ref key, value) in &pairs {
72+
map.entry(&**key, value);
73+
}
74+
75+
let mut initializer_bytes = Vec::<u8>::new();
76+
let mut initializer_tokens = quote::Tokens::new();
77+
map.build(&mut initializer_bytes).unwrap();
78+
initializer_tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());
79+
80+
let tokens = quote! {
81+
impl #name {
82+
#[inline]
83+
fn map() -> &'static ::phf::Map<&'static str, #value_type> {
84+
static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens;
85+
&MAP
86+
}
87+
}
88+
};
89+
90+
tokens.as_str().parse().unwrap()
91+
}
92+
93+
/// Panic if the first attribute isn’t `#[foo(…)]` with the given name,
94+
/// or return the parameters.
95+
fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] {
96+
for attr in &input.attrs {
97+
match attr.value {
98+
syn::MetaItem::List(ref name, ref nested) if name == expected_name => {
99+
return nested
100+
}
101+
_ => {}
102+
}
103+
}
104+
panic!("expected a {} attribute", expected_name)
105+
}
106+
107+
/// Panic if `sub_attr` is not a name-value like `foo = "…"` with the given name,
108+
/// or return the value.
109+
fn sub_attr_value<'a>(sub_attr: &'a syn::NestedMetaItem, expected_name: &str) -> &'a str {
110+
match *sub_attr {
111+
syn::NestedMetaItem::MetaItem(
112+
syn::MetaItem::NameValue(ref name, syn::Lit::Str(ref value, _))
113+
)
114+
if name == expected_name => {
115+
value
116+
}
117+
_ => {
118+
panic!("expected a `{} = \"\"` parameter to the attribute, got {:?}",
119+
expected_name, sub_attr)
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)