Skip to content

Commit 7f9e876

Browse files
committed
Support full match syntax in match_ignore_ascii_case
1 parent d517090 commit 7f9e876

File tree

2 files changed

+93
-79
lines changed

2 files changed

+93
-79
lines changed

macros/lib.rs

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,58 @@ extern crate syn;
1010
use std::ascii::AsciiExt;
1111

1212
/// Panic if any string contains ASCII uppercase letters.
13-
#[proc_macro_derive(cssparser__assert_ascii_lowercase)]
14-
pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15-
let input = syn::parse_macro_input(&input.to_string()).unwrap();
16-
17-
for token in find_smuggled_tokens(&input) {
18-
let string = string_literal(token);
19-
assert_eq!(*string, string.to_ascii_lowercase(),
20-
"the string patterns must be given in ASCII lowercase");
21-
}
22-
23-
"".parse().unwrap()
13+
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
14+
#[proc_macro_derive(cssparser__assert_ascii_lowercase__max_len)]
15+
pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
16+
max_len_common(input, |token_trees| {
17+
let tokens = quote!( match x { #( #token_trees )* } );
18+
let expr = syn::parse_expr(tokens.as_str()).unwrap();
19+
let arms = match expr {
20+
syn::Expr { node: syn::ExprKind::Match(_, ref arms), .. } => arms,
21+
_ => panic!("expected a match expression, got {:?}", expr)
22+
};
23+
arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| {
24+
let expr = match *pattern {
25+
syn::Pat::Lit(ref expr) => expr,
26+
syn::Pat::Wild |
27+
syn::Pat::Ident(_, _, None) => return None,
28+
syn::Pat::Ident(_, _, Some(ref sub_pattern)) => {
29+
match **sub_pattern {
30+
syn::Pat::Lit(ref expr) => expr,
31+
syn::Pat::Wild => return None,
32+
_ => panic!("expected string or wildcard pattern, got {:?}", pattern)
33+
}
34+
}
35+
_ => panic!("expected string or wildcard pattern, got {:?}", pattern)
36+
};
37+
match **expr {
38+
syn::Expr { node: syn::ExprKind::Lit(syn::Lit::Str(ref string, _)), .. } => {
39+
assert_eq!(*string, string.to_ascii_lowercase(),
40+
"string patterns must be given in ASCII lowercase");
41+
Some(string.len())
42+
}
43+
_ => panic!("expected string pattern, got {:?}", expr)
44+
}
45+
}).max()
46+
})
2447
}
2548

2649
/// Emit a `MAX_LENGTH` constant with the length of the longest string.
2750
#[proc_macro_derive(cssparser__max_len)]
2851
pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
29-
let input = syn::parse_macro_input(&input.to_string()).unwrap();
30-
31-
let token_trees = find_smuggled_tokens(&input);
32-
let lengths = token_trees.iter().map(|tt| string_literal(tt).len());
33-
let max_length = lengths.max().expect("expected at least one string");
34-
35-
let tokens = quote! {
36-
const MAX_LENGTH: usize = #max_length;
37-
};
52+
max_len_common(input, |token_trees| {
53+
token_trees.iter().map(|tt| string_literal(tt).len()).max()
54+
})
55+
}
3856

39-
tokens.as_str().parse().unwrap()
57+
fn max_len_common<F>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
58+
where F : FnOnce(&[syn::TokenTree]) -> Option<usize> {
59+
common(input, |token_trees| {
60+
let max_length = f(token_trees).expect("expected at least one string");
61+
quote! {
62+
const MAX_LENGTH: usize = #max_length;
63+
}
64+
})
4065
}
4166

4267
/// ```
@@ -46,29 +71,35 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
4671
/// Map keys are ASCII-lowercased.
4772
#[proc_macro_derive(cssparser__phf_map)]
4873
pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
49-
let input = syn::parse_macro_input(&input.to_string()).unwrap();
74+
common(input, |token_trees| {
75+
let value_type = &token_trees[0];
76+
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
77+
let key = string_literal(&chunk[0]);
78+
let value = &chunk[1];
79+
(key.to_ascii_lowercase(), quote!(#value).to_string())
80+
}).collect();
81+
82+
let mut map = phf_codegen::Map::new();
83+
for &(ref key, ref value) in &pairs {
84+
map.entry(&**key, &**value);
85+
}
86+
87+
let mut tokens = quote! {
88+
static MAP: ::phf::Map<&'static str, #value_type> =
89+
};
90+
let mut initializer_bytes = Vec::new();
91+
map.build(&mut initializer_bytes).unwrap();
92+
tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());
93+
tokens.append(";");
94+
tokens
95+
})
96+
}
5097

98+
fn common<F>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
99+
where F: FnOnce(&[syn::TokenTree]) -> quote::Tokens {
100+
let input = syn::parse_macro_input(&input.to_string()).unwrap();
51101
let token_trees = find_smuggled_tokens(&input);
52-
let value_type = &token_trees[0];
53-
let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| {
54-
let key = string_literal(&chunk[0]);
55-
let value = &chunk[1];
56-
(key.to_ascii_lowercase(), quote!(#value).to_string())
57-
}).collect();
58-
59-
let mut map = phf_codegen::Map::new();
60-
for &(ref key, ref value) in &pairs {
61-
map.entry(&**key, &**value);
62-
}
63-
64-
let mut tokens = quote! {
65-
static MAP: ::phf::Map<&'static str, #value_type> =
66-
};
67-
let mut initializer_bytes = Vec::new();
68-
map.build(&mut initializer_bytes).unwrap();
69-
tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap());
70-
tokens.append(";");
71-
102+
let tokens = f(token_trees);
72103
tokens.as_str().parse().unwrap()
73104
}
74105

src/macros.rs

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,50 +24,34 @@
2424
/// "rgba" => parse_rgba(..),
2525
/// "hsl" => parse_hsl(..),
2626
/// "hsla" => parse_hsla(..),
27-
/// _ => Err("unknown function")
27+
/// name @ _ => Err(format!("unknown function: {}", name))
2828
/// }
2929
/// # ;}
3030
/// # use std::ops::RangeFull;
31-
/// # fn parse_rgb(_: RangeFull) -> Result<(), &'static str> { Err("") }
32-
/// # fn parse_rgba(_: RangeFull) -> Result<(), &'static str> { Err("") }
33-
/// # fn parse_hsl(_: RangeFull) -> Result<(), &'static str> { Err("") }
34-
/// # fn parse_hsla(_: RangeFull) -> Result<(), &'static str> { Err("") }
31+
/// # fn parse_rgb(_: RangeFull) -> Result<(), String> { Ok(()) }
32+
/// # fn parse_rgba(_: RangeFull) -> Result<(), String> { Ok(()) }
33+
/// # fn parse_hsl(_: RangeFull) -> Result<(), String> { Ok(()) }
34+
/// # fn parse_hsla(_: RangeFull) -> Result<(), String> { Ok(()) }
3535
/// ```
3636
#[macro_export]
3737
macro_rules! match_ignore_ascii_case {
38-
// parse the last case plus the fallback
39-
(@inner $value:expr, ($string:expr => $result:expr, _ => $fallback:expr) -> ($($parsed:tt)*) ) => {
40-
match_ignore_ascii_case!(@inner $value, () -> ($($parsed)* ($string => $result)) $fallback)
41-
};
42-
43-
// parse a case (not the last one)
44-
(@inner $value:expr, ($string:expr => $result:expr, $($rest:tt)*) -> ($($parsed:tt)*) ) => {
45-
match_ignore_ascii_case!(@inner $value, ($($rest)*) -> ($($parsed)* ($string => $result)))
46-
};
47-
48-
// finished parsing
49-
(@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => {
38+
( $input:expr, $( $match_body:tt )* ) => {
5039
{
5140
_cssparser_internal__pretend_proc_macro! {
52-
cssparser__assert_ascii_lowercase!( $( $string )+ )
41+
cssparser__assert_ascii_lowercase__max_len!( $( $match_body )* )
5342
}
5443

5544
{
56-
_cssparser_internal__to_lowercase!($value => lowercase, $($string),+);
57-
match lowercase {
58-
$(
59-
Some($string) => $result,
60-
)+
61-
_ => $fallback
45+
// MAX_LENGTH is generated by cssparser__assert_ascii_lowercase__max_len
46+
_cssparser_internal__to_lowercase!($input, MAX_LENGTH => lowercase);
47+
// "A" is a short string that we know is different for every string pattern,
48+
// since we’ve verified that none of them include ASCII upper case letters.
49+
match lowercase.unwrap_or("A") {
50+
$( $match_body )*
6251
}
6352
}
6453
}
6554
};
66-
67-
// entry point, start parsing
68-
( $value:expr, $($rest:tt)* ) => {
69-
match_ignore_ascii_case!(@inner $value, ($($rest)*) -> ())
70-
};
7155
}
7256

7357
/// Define a function `$name(&str) -> Option<&'static $ValueType>`
@@ -107,7 +91,11 @@ macro_rules! ascii_case_insensitive_phf_map {
10791
}
10892

10993
{
110-
_cssparser_internal__to_lowercase!(input => lowercase, $($key),+);
94+
_cssparser_internal__pretend_proc_macro! {
95+
cssparser__max_len!( $( $key )+ )
96+
}
97+
// MAX_LENGTH is generated by cssparser__max_len
98+
_cssparser_internal__to_lowercase!(input, MAX_LENGTH => lowercase);
11199
lowercase.and_then(|s| MAP.get(s))
112100
}
113101
}
@@ -124,17 +112,12 @@ macro_rules! ascii_case_insensitive_phf_map {
124112
#[macro_export]
125113
#[doc(hidden)]
126114
macro_rules! _cssparser_internal__to_lowercase {
127-
($input: expr => $output: ident, $($string: expr),+) => {
128-
_cssparser_internal__pretend_proc_macro! {
129-
cssparser__max_len!( $( $string )+ )
130-
}
131-
115+
($input: expr, $MAX_LENGTH: expr => $output: ident) => {
132116
// mem::uninitialized() is ok because `buffer` is only used in `_internal__to_lowercase`,
133117
// which initializes with `copy_from_slice` the part of the buffer it uses,
134118
// before it uses it.
135119
#[allow(unsafe_code)]
136-
// MAX_LENGTH is generated by cssparser__max_len
137-
let mut buffer: [u8; MAX_LENGTH] = unsafe {
120+
let mut buffer: [u8; $MAX_LENGTH] = unsafe {
138121
::std::mem::uninitialized()
139122
};
140123
let input: &str = $input;

0 commit comments

Comments
 (0)