Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add a ascii_case_insensitive_phf_map macro, use it for color keywords.
Fix #115.
  • Loading branch information
SimonSapin committed Feb 25, 2017
commit 0192030d8d4ef19fe43c8bb3f28df0f5f372a72f
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ encoding_rs = "0.5"
cssparser-macros = {path = "./macros", version = "0.1"}
heapsize = {version = "0.3", optional = true}
matches = "0.1"
phf = "0.7"
serde = {version = "0.9", optional = true}

[build-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ path = "lib.rs"
proc-macro = true

[dependencies]
syn = "0.11"
phf_codegen = "0.7"
quote = "0.3"
syn = "0.11"

100 changes: 75 additions & 25 deletions macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,96 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate phf_codegen;
extern crate proc_macro;
#[macro_use] extern crate quote;
extern crate syn;

use std::ascii::AsciiExt;

#[proc_macro_derive(cssparser__match_ignore_ascii_case__derive,
#[proc_macro_derive(cssparser__match_ignore_ascii_case__max_len,
attributes(cssparser__match_ignore_ascii_case__data))]
pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input(&input.to_string()).unwrap();
let data = list_attr(&input, "cssparser__match_ignore_ascii_case__data");

let max_length;
let lengths = data.iter().map(|sub_attr| {
let string = sub_attr_value(sub_attr, "string");
assert_eq!(*string, string.to_ascii_lowercase(),
"the expected strings must be given in ASCII lowercase");
string.len()
});
let max_length = lengths.max().expect("expected at least one string");

match input.attrs[0].value {
syn::MetaItem::List(ref ident, ref nested)
if ident == "cssparser__match_ignore_ascii_case__data" => {
let lengths = nested.iter().map(|sub_attr| match *sub_attr {
syn::NestedMetaItem::MetaItem(
syn::MetaItem::NameValue(ref ident, syn::Lit::Str(ref string, _))
)
if ident == "string" => {
assert_eq!(*string, string.to_ascii_lowercase(),
"the expected strings must be given in ASCII lowercase");
string.len()
}
_ => {
panic!("expected a `string = \"…\" parameter to the attribute, got {:?}", sub_attr)
}
});

max_length = lengths.max().expect("expected at least one string")
}
_ => {
panic!("expected a cssparser_match_ignore_ascii_case_data attribute")
let tokens = quote! {
const MAX_LENGTH: usize = #max_length;
};

tokens.as_str().parse().unwrap()
}


#[proc_macro_derive(cssparser__phf_map,
attributes(cssparser__phf_map__kv_pairs))]
pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input(&input.to_string()).unwrap();
let name = &input.ident;
let value_type = match input.body {
syn::Body::Struct(syn::VariantData::Tuple(ref fields)) if fields.len() == 1 => {
&fields[0].ty
}
_ => panic!("expected tuple struct newtype, got {:?}", input.body)
};

let kv_pairs = list_attr(&input, "cssparser__phf_map__kv_pairs");

let mut map = phf_codegen::Map::new();
for chunk in kv_pairs.chunks(2) {
let key = sub_attr_value(&chunk[0], "key");
let value = sub_attr_value(&chunk[1], "value");
map.entry(key, value);
}

let mut initializer_bytes = Vec::<u8>::new();
let mut initializer_tokens = quote::Tokens::new();
map.build(&mut initializer_bytes).unwrap();
initializer_tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());

let tokens = quote! {
const MAX_LENGTH: usize = #max_length;
impl #name {
#[inline]
fn map() -> &'static ::phf::Map<&'static str, #value_type> {
static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens;
&MAP
}
}
};

tokens.as_str().parse().unwrap()
}

fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] {
match input.attrs[0].value {
syn::MetaItem::List(ref name, ref nested) if name == expected_name => {
nested
}
_ => {
panic!("expected a {} attribute", expected_name)
}
}
}

fn sub_attr_value<'a>(sub_attr: &'a syn::NestedMetaItem, expected_name: &str) -> &'a str {
match *sub_attr {
syn::NestedMetaItem::MetaItem(
syn::MetaItem::NameValue(ref name, syn::Lit::Str(ref value, _))
)
if name == expected_name => {
value
}
_ => {
panic!("expected a `{} = \"…\"` parameter to the attribute, got {:?}",
expected_name, sub_attr)
}
}
}
Loading