Skip to content

Make match_ignore_ascii_case more efficient, add ascii_case_insensitive_phf_map #122

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 14 commits into from
Feb 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 9 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]

name = "cssparser"
version = "0.10.0"
version = "0.11.0"
authors = [ "Simon Sapin <[email protected]>" ]

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

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

[lib]
doctest = false

[dev-dependencies]
rustc-serialize = "0.3"
tempdir = "0.3"
encoding_rs = "0.3.2"
encoding_rs = "0.5"

[dependencies]
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]
syn = { version = "0.10.6", features = ["full", "visit"]}
syn = "0.11"
quote = "0.3"

[features]
bench = []
dummy_match_byte = []

[workspace]
members = [".", "./macros"]
18 changes: 18 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "cssparser-macros"
version = "0.1.0"
authors = ["Simon Sapin <[email protected]>"]
description = "Procedural macros for cssparser"
documentation = "https://docs.rs/cssparser-macros/"
repository = "https://github.com/servo/rust-cssparser"
license = "MPL-2.0"

[lib]
path = "lib.rs"
proc-macro = true

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

122 changes: 122 additions & 0 deletions macros/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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;

/// 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");
}

"".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);
}

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
}
}
};

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

/// 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)
}
}
}
Loading