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
Next Next commit
Make phf map macro take literal values rather than strings of Rust sy…
  • Loading branch information
SimonSapin committed Feb 27, 2017
commit 8c2f499939a0fbf2eeec055d309ebf314d803acb
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

name = "cssparser"
version = "0.11.0"
version = "0.12.0"
authors = [ "Simon Sapin <simon.sapin@exyr.org>" ]

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

[dependencies]
cssparser-macros = {path = "./macros", version = "0.1"}
cssparser-macros = {path = "./macros", version = "0.2"}
heapsize = {version = "0.3", optional = true}
matches = "0.1"
phf = "0.7"
Expand Down
5 changes: 2 additions & 3 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cssparser-macros"
version = "0.1.0"
version = "0.2.0"
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
description = "Procedural macros for cssparser"
documentation = "https://docs.rs/cssparser-macros/"
Expand All @@ -14,5 +14,4 @@ proc-macro = true
[dependencies]
phf_codegen = "0.7"
quote = "0.3"
syn = "0.11"

syn = {version = "0.11", features = ["full"]}
75 changes: 55 additions & 20 deletions macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,31 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
tokens.as_str().parse().unwrap()
}

/// On `struct $Name($ValueType)`, add a new static method
/// `fn map() -> &'static ::phf::Map<&'static str, $ValueType>`.
/// The map’s content is given as:
/// `#[cssparser__phf_map__kv_pairs(key = "…", value = "…", key = "…", value = "…")]`.
/// Keys are ASCII-lowercased.
#[proc_macro_derive(cssparser__phf_map,
attributes(cssparser__phf_map__kv_pairs))]
/// ```
/// impl $Name {
/// fn map() -> &'static ::phf::Map<&'static str, $ValueType> { … }
/// }
/// ```
///
/// Map keys are ASCII-lowercased.
#[proc_macro_derive(cssparser__phf_map)]
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 pairs: Vec<_> = list_attr(&input, "cssparser__phf_map__kv_pairs").chunks(2).map(|chunk| {
let key = sub_attr_value(&chunk[0], "key");
let value = sub_attr_value(&chunk[1], "value");
(key.to_ascii_lowercase(), value)
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 value = &chunk[1];
(key.to_ascii_lowercase(), quote!(#value).to_string())
}).collect();

let mut map = phf_codegen::Map::new();
for &(ref key, value) in &pairs {
map.entry(&**key, value);
for &(ref key, ref value) in &pairs {
map.entry(&**key, &**value);
}

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

/// Return the `…` part in:
///
/// ```rust
/// enum $Name {
/// Input = (0, stringify!(…)).0
/// }
/// ```
fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] {
let discriminant = match input.body {
syn::Body::Enum(ref variants) if variants.len() == 1 => &variants[0].discriminant,
_ => panic!("expected single-variant enum, got {:?}", input.body)
};
let tuple = match *discriminant {
Some(syn::ConstExpr::Other(syn::Expr { node: syn::ExprKind::TupField(ref t, 0), .. })) => t,
_ => panic!("expected a discriminant like tuple.0, got {:?}", discriminant)
};
let expr = match **tuple {
syn::Expr { node: syn::ExprKind::Tup(ref values), .. } if values.len() == 2 => &values[1],
_ => panic!("expected non-empty tuple, got {:?}", tuple)
};
let macro_args = match *expr {
syn::Expr { node: syn::ExprKind::Mac(
syn::Mac { ref tts, path: syn::Path { global: false, ref segments }}
), .. }
if segments.len() == 1
&& segments[0] == syn::PathSegment::from("stringify")
&& tts.len() == 1
=> &tts[0],
_ => panic!("expected a stringify!(…) macro, got {:?}", expr)
};
match *macro_args {
syn::TokenTree::Delimited(syn::Delimited { ref tts, delim: syn::DelimToken::Paren }) => tts,
_ => panic!("expected (…) parentheses, got {:?}", macro_args)
}
}

/// 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] {
Expand Down
Loading