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 all commits
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
7 changes: 4 additions & 3 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,10 +20,11 @@ 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"
procedural-masquerade = {path = "./procedural-masquerade", version = "0.1"}
serde = {version = "0.9", optional = true}

[build-dependencies]
Expand All @@ -35,4 +36,4 @@ bench = []
dummy_match_byte = []

[workspace]
members = [".", "./macros"]
members = [".", "./macros", "./procedural-masquerade"]
10 changes: 5 additions & 5 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ mod codegen {
}

#[cfg(not(feature = "dummy_match_byte"))]
#[path = "src/macros/mod.rs"]
mod macros;
#[path = "build/match_byte.rs"]
mod match_byte;

#[cfg(not(feature = "dummy_match_byte"))]
mod codegen {
use macros;
use match_byte;
use std::env;
use std::path::Path;

pub fn main(tokenizer_rs: &Path) {
macros::match_byte::expand(tokenizer_rs,
&Path::new(&env::var("OUT_DIR").unwrap()).join("tokenizer.rs"));
match_byte::expand(tokenizer_rs,
&Path::new(&env::var("OUT_DIR").unwrap()).join("tokenizer.rs"));

}
}
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 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 @@ -12,7 +12,7 @@ path = "lib.rs"
proc-macro = true

[dependencies]
procedural-masquerade = {path = "../procedural-masquerade", version = "0.1"}
phf_codegen = "0.7"
quote = "0.3"
syn = "0.11"

quote = "0.3.14"
syn = {version = "0.11.8", features = ["full"]}
181 changes: 82 additions & 99 deletions macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,104 @@
* 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/. */

#[macro_use] extern crate procedural_masquerade;
extern crate phf_codegen;
extern crate proc_macro;
#[macro_use] extern crate quote;
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))]
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");
assert_eq!(*string, string.to_ascii_lowercase(),
"the expected strings must be given in ASCII lowercase");
define_proc_macros! {
/// Input: the arms of a `match` expression.
///
/// Output: a `MAX_LENGTH` constant with the length of the longest string pattern.
///
/// Panic if the arms contain non-string patterns,
/// or string patterns that contains ASCII uppercase letters.
#[allow(non_snake_case)]
pub fn cssparser_internal__assert_ascii_lowercase__max_len(input: &str) -> String {
let expr = syn::parse_expr(&format!("match x {{ {} }}", input)).unwrap();
let arms = match expr {
syn::Expr { node: syn::ExprKind::Match(_, ref arms), .. } => arms,
_ => panic!("expected a match expression, got {:?}", expr)
};
max_len(arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| {
let expr = match *pattern {
syn::Pat::Lit(ref expr) => expr,
syn::Pat::Wild |
syn::Pat::Ident(_, _, None) => return None,
syn::Pat::Ident(_, _, Some(ref sub_pattern)) => {
match **sub_pattern {
syn::Pat::Lit(ref expr) => expr,
syn::Pat::Wild => return None,
_ => panic!("expected string or wildcard pattern, got {:?}", pattern)
}
}
_ => panic!("expected string or wildcard pattern, got {:?}", pattern)
};
match **expr {
syn::Expr { node: syn::ExprKind::Lit(syn::Lit::Str(ref string, _)), .. } => {
assert_eq!(*string, string.to_ascii_lowercase(),
"string patterns must be given in ASCII lowercase");
Some(string.len())
}
_ => panic!("expected string pattern, got {:?}", expr)
}
}))
}

"".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))]
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 max_length = lengths.max().expect("expected at least one string");

let tokens = quote! {
const MAX_LENGTH: usize = #max_length;
};

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))]
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)
}).collect();

let mut map = phf_codegen::Map::new();
for &(ref key, value) in &pairs {
map.entry(&**key, value);
/// Input: string literals with no separator
///
/// Output: a `MAX_LENGTH` constant with the length of the longest string.
#[allow(non_snake_case)]
pub fn cssparser_internal__max_len(input: &str) -> String {
max_len(syn::parse_token_trees(input).unwrap().iter().map(|tt| string_literal(tt).len()))
}

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! {
impl #name {
#[inline]
fn map() -> &'static ::phf::Map<&'static str, #value_type> {
static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens;
&MAP
}
/// Input: parsed as token trees. The first TT is a type. (Can be wrapped in parens.)
/// following TTs are grouped in pairs, each pair being a key as a string literal
/// and the corresponding value as a const expression.
///
/// Output: a rust-phf map, with keys ASCII-lowercased:
/// ```
/// static MAP: &'static ::cssparser::phf::Map<&'static str, $ValueType> = …;
/// ```
#[allow(non_snake_case)]
pub fn cssparser_internal__phf_map(input: &str) -> String {
let token_trees = syn::parse_token_trees(input).unwrap();
let value_type = &token_trees[0];
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
let key = string_literal(&chunk[0]);
let value = &chunk[1];
(key.to_ascii_lowercase(), quote!(#value).to_string())
}).collect();

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

tokens.as_str().parse().unwrap()
let mut tokens = quote! {
static MAP: ::cssparser::_internal__phf::Map<&'static str, #value_type> =
};
let mut initializer_bytes = Vec::new();
map.build(&mut initializer_bytes).unwrap();
tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());
tokens.append(";");
tokens.into_string()
}
}

/// 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)
fn max_len<I: Iterator<Item=usize>>(lengths: I) -> String {
let max_length = lengths.max().expect("expected at least one string");
quote!( const MAX_LENGTH: usize = #max_length; ).into_string()
}

/// 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)
}
}
12 changes: 12 additions & 0 deletions procedural-masquerade/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "procedural-masquerade"
version = "0.1.1"
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
description = "macro_rules for making proc_macro_derive pretending to be proc_macro"
documentation = "https://docs.rs/procedural-masquerade/"
repository = "https://github.com/servo/rust-cssparser"
license = "MIT/Apache-2.0"

[lib]
path = "lib.rs"
doctest = false
Loading