Skip to content

Commit f23eba5

Browse files
committed
Add An+B parsing.
1 parent 77b75fd commit f23eba5

File tree

4 files changed

+134
-5
lines changed

4 files changed

+134
-5
lines changed

ast.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,17 @@ pub trait SkipWhitespaceIterable<'self> {
134134

135135
impl<'self> SkipWhitespaceIterable<'self> for &'self [ComponentValue] {
136136
pub fn skip_whitespace(self) -> SkipWhitespaceIterator<'self> {
137-
SkipWhitespaceIterator{ iter: self.iter() }
137+
SkipWhitespaceIterator{ iter_with_whitespace: self.iter() }
138138
}
139139
}
140140

141-
struct SkipWhitespaceIterator<'self> {
142-
iter: vec::VecIterator<'self, ComponentValue>,
141+
pub struct SkipWhitespaceIterator<'self> {
142+
iter_with_whitespace: vec::VecIterator<'self, ComponentValue>,
143143
}
144144

145145
impl<'self> Iterator<&'self ComponentValue> for SkipWhitespaceIterator<'self> {
146146
fn next(&mut self) -> Option<&'self ComponentValue> {
147-
for component_value in self.iter {
147+
for component_value in self.iter_with_whitespace {
148148
if component_value != &WhiteSpace { return Some(component_value) }
149149
}
150150
None
@@ -162,7 +162,7 @@ impl ConsumeSkipWhitespaceIterable for ~[ComponentValue] {
162162
}
163163
}
164164

165-
struct ConsumeSkipWhitespaceIterator {
165+
pub struct ConsumeSkipWhitespaceIterator {
166166
iter: vec::ConsumeIterator<ComponentValue>,
167167
}
168168

cssparser.rc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ pub use ast::*;
1111
pub use tokenizer::*;
1212
pub use parser::*;
1313
pub use color::*;
14+
pub use nth::*;
1415

1516
mod ast;
1617
mod tokenizer;
1718
mod parser;
1819
mod color;
20+
mod nth;
1921

2022
#[cfg(test)]
2123
mod tests;

nth.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use std::i32;
2+
use std::ascii::to_ascii_lower;
3+
use ast::*;
4+
5+
6+
/// Parse the An+B notation, as found in the ``:nth-child()`` selector.
7+
/// The input is typically the arguments of a function component value.
8+
/// Return Some((A, B)), or None for a syntax error.
9+
pub fn parse_nth(input: &[ComponentValue]) -> Option<(i32, i32)> {
10+
let iter = &mut input.skip_whitespace();
11+
match iter.next() {
12+
Some(&Number(ref value)) => match value.int_value {
13+
Some(b) => parse_end(iter, 0, b as i32),
14+
_ => None,
15+
},
16+
Some(&Dimension(ref value, ref unit)) => match value.int_value {
17+
Some(a) => {
18+
let unit: &str = to_ascii_lower(unit.as_slice());
19+
match unit {
20+
"n" => parse_b(iter, a as i32),
21+
"n-" => parse_signless_b(iter, a as i32, -1),
22+
_ => match(parse_n_dash_digits(unit)) {
23+
Some(b) => parse_end(iter, a as i32, b),
24+
_ => None
25+
},
26+
}
27+
},
28+
_ => None,
29+
},
30+
Some(&Ident(ref value)) => {
31+
let ident: &str = to_ascii_lower(value.as_slice());
32+
match ident {
33+
"even" => parse_end(iter, 2, 0),
34+
"odd" => parse_end(iter, 2, 1),
35+
"n" => parse_b(iter, 1),
36+
"-n" => parse_b(iter, -1),
37+
"n-" => parse_signless_b(iter, 1, -1),
38+
"-n-" => parse_signless_b(iter, -1, -1),
39+
_ if ident.starts_with("-") => match(parse_n_dash_digits(ident.slice_from(1))) {
40+
Some(b) => parse_end(iter, -1, b),
41+
_ => None
42+
},
43+
_ => match(parse_n_dash_digits(ident)) {
44+
Some(b) => parse_end(iter, 1, b),
45+
_ => None
46+
},
47+
}
48+
},
49+
Some(&Delim('+')) => match iter.iter_with_whitespace.next() {
50+
Some(&Ident(ref value)) => {
51+
let ident: &str = to_ascii_lower(value.as_slice());
52+
match ident {
53+
"n" => parse_b(iter, 1),
54+
"n-" => parse_signless_b(iter, 1, -1),
55+
_ => match(parse_n_dash_digits(ident)) {
56+
Some(b) => parse_end(iter, 1, b),
57+
_ => None
58+
},
59+
}
60+
},
61+
_ => None
62+
},
63+
_ => None
64+
}
65+
}
66+
67+
68+
type Nth = Option<(i32, i32)>;
69+
type Iter<'self> = SkipWhitespaceIterator<'self>;
70+
71+
fn parse_b(iter: &mut Iter, a: i32) -> Nth {
72+
match iter.next() {
73+
None => Some((a, 0)),
74+
Some(&Delim('+')) => parse_signless_b(iter, a, 1),
75+
Some(&Delim('-')) => parse_signless_b(iter, a, -1),
76+
Some(&Number(ref value)) => match value.int_value {
77+
Some(b) if has_sign(value) => parse_end(iter, a, b as i32),
78+
_ => None,
79+
},
80+
_ => None
81+
}
82+
}
83+
84+
fn parse_signless_b(iter: &mut Iter, a: i32, b_sign: i32) -> Nth {
85+
match iter.next() {
86+
Some(&Number(ref value)) => match value.int_value {
87+
Some(b) if !has_sign(value) => parse_end(iter, a, b_sign * (b as i32)),
88+
_ => None,
89+
},
90+
_ => None
91+
}
92+
}
93+
94+
fn parse_end(iter: &mut Iter, a: i32, b: i32) -> Nth {
95+
match iter.next() {
96+
None => Some((a, b)),
97+
Some(_) => None,
98+
}
99+
}
100+
101+
fn parse_n_dash_digits(string: &str) -> Option<i32> {
102+
if string.len() >= 3
103+
&& string.starts_with("n-")
104+
&& string.slice_from(2).iter().all(|c| match c { '0'..'9' => true, _ => false })
105+
{
106+
let result = i32::from_str(string.slice_from(1)); // Include the minus sign
107+
assert!(result.is_some());
108+
result
109+
}
110+
else { None }
111+
}
112+
113+
#[inline]
114+
fn has_sign(value: &NumericValue) -> bool {
115+
match value.representation[0] as char {
116+
'+' | '-' => true,
117+
_ => false
118+
}
119+
}

tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ fn color3_keywords() {
164164
}
165165
166166
167+
#[test]
168+
fn nth() {
169+
do run_json_tests(include_str!("css-parsing-tests/An+B.json")) |input| {
170+
parse_nth(tokenize(input).transform(|(c, _)| c).to_owned_vec())
171+
}
172+
}
173+
174+
167175
impl ToJson for Result<Rule, SyntaxError> {
168176
fn to_json(&self) -> json::Json {
169177
match *self {

0 commit comments

Comments
 (0)