Skip to content

Rewrite procedural macro based on the enum discriminant trick #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Feb 28, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
Use the enum discriminant trick for match_ignore_ascii_case too
  • Loading branch information
SimonSapin committed Feb 27, 2017
commit 0ead1b54496dbb1e4d575d876736a0452e8dcc28
59 changes: 14 additions & 45 deletions macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,27 @@ extern crate syn;

use std::ascii::AsciiExt;

/// Find a `#[cssparser__assert_ascii_lowercase__data(string = "…", string = "…")]` attribute,
/// and panic if any string contains ASCII uppercase letters.
#[proc_macro_derive(cssparser__assert_ascii_lowercase,
attributes(cssparser__assert_ascii_lowercase__data))]
/// Panic if any string contains ASCII uppercase letters.
#[proc_macro_derive(cssparser__assert_ascii_lowercase)]
pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input(&input.to_string()).unwrap();
let data = list_attr(&input, "cssparser__assert_ascii_lowercase__data");

for sub_attr in data {
let string = sub_attr_value(sub_attr, "string");
for token in find_smuggled_tokens(&input) {
let string = string_literal(token);
assert_eq!(*string, string.to_ascii_lowercase(),
"the expected strings must be given in ASCII lowercase");
"the string patterns must be given in ASCII lowercase");
}

"".parse().unwrap()
}

/// Find a `#[cssparser__max_len__data(string = "…", string = "…")]` attribute,
/// and emit a `MAX_LENGTH` constant with the length of the longest string.
#[proc_macro_derive(cssparser__max_len,
attributes(cssparser__max_len__data))]
#[proc_macro_derive(cssparser__max_len)]
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__max_len__data");

let lengths = data.iter().map(|sub_attr| sub_attr_value(sub_attr, "string").len());
let token_trees = find_smuggled_tokens(&input);
let lengths = token_trees.iter().map(|tt| string_literal(tt).len());
let max_length = lengths.max().expect("expected at least one string");

let tokens = quote! {
Expand All @@ -55,13 +50,11 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
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 token_trees = find_smuggled_tokens(&input);
let value_type = &token_trees[0];
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
let key = match chunk[0] {
syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string,
_ => panic!("expected string literal, got {:?}", chunk[0])
};
let key = string_literal(&chunk[0]);
let value = &chunk[1];
(key.to_ascii_lowercase(), quote!(#value).to_string())
}).collect();
Expand Down Expand Up @@ -125,33 +118,9 @@ fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] {
}
}

/// Panic if the first attribute isn’t `#[foo(…)]` with the given name,
/// or return the parameters.
fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] {
for attr in &input.attrs {
match attr.value {
syn::MetaItem::List(ref name, ref nested) if name == expected_name => {
return nested
}
_ => {}
}
}
panic!("expected a {} attribute", expected_name)
}

/// Panic if `sub_attr` is not a name-value like `foo = "…"` with the given name,
/// or return the value.
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)
}
fn string_literal(token: &syn::TokenTree) -> &str {
match *token {
syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string,
_ => panic!("expected string literal, got {:?}", token)
}
}
14 changes: 8 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,10 @@ macro_rules! match_ignore_ascii_case {
(@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => {
{
#[derive(cssparser__assert_ascii_lowercase)]
#[cssparser__assert_ascii_lowercase__data($(string = $string),+)]
#[allow(dead_code)]
struct Dummy;
#[allow(unused)]
enum Dummy {
Input = (0, stringify!( $( $string )+ )).0
}

_cssparser_internal__to_lowercase!($value => lowercase, $($string),+);
match lowercase {
Expand Down Expand Up @@ -216,9 +217,10 @@ macro_rules! ascii_case_insensitive_phf_map {
macro_rules! _cssparser_internal__to_lowercase {
($input: expr => $output: ident, $($string: expr),+) => {
#[derive(cssparser__max_len)]
#[cssparser__max_len__data($(string = $string),+)]
#[allow(dead_code)]
struct Dummy2;
#[allow(unused)]
enum Dummy2 {
Input = (0, stringify!( $( $string )+ )).0
}

// mem::uninitialized() is ok because `buffer` is only used in `_internal__to_lowercase`,
// which initializes with `copy_from_slice` the part of the buffer it uses,
Expand Down