3 // Copyright (c) 2018 Guillaume Gomez
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 use std
::iter
::Peekable
;
25 use std
::str::CharIndices
;
27 pub trait MyTryFrom
<T
>: Sized
{
29 fn try_from(value
: T
) -> Result
<Self, Self::Error
>;
32 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
33 pub enum ReservedChar
{
59 impl fmt
::Display
for ReservedChar
{
60 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
63 ReservedChar
::Comma
=> '
,'
,
64 ReservedChar
::OpenParenthese
=> '
('
,
65 ReservedChar
::CloseParenthese
=> '
)'
,
66 ReservedChar
::OpenCurlyBrace
=> '
{'
,
67 ReservedChar
::CloseCurlyBrace
=> '
}'
,
68 ReservedChar
::OpenBracket
=> '
['
,
69 ReservedChar
::CloseBracket
=> '
]'
,
70 ReservedChar
::Colon
=> '
:'
,
71 ReservedChar
::SemiColon
=> '
;'
,
72 ReservedChar
::Slash
=> '
/'
,
73 ReservedChar
::Star
=> '
*'
,
74 ReservedChar
::Plus
=> '
+'
,
75 ReservedChar
::EqualSign
=> '
='
,
76 ReservedChar
::Space
=> ' '
,
77 ReservedChar
::Tab
=> '
\t'
,
78 ReservedChar
::Backline
=> '
\n'
,
79 ReservedChar
::SuperiorThan
=> '
>'
,
80 ReservedChar
::Quote
=> '
\''
,
81 ReservedChar
::DoubleQuote
=> '
"',
82 ReservedChar::Pipe => '|',
83 ReservedChar::Tilde => '~',
84 ReservedChar::Dollar => '$',
85 ReservedChar::Circumflex => '^',
90 impl MyTryFrom<char> for ReservedChar {
91 type Error = &'static str;
93 fn try_from(value: char) -> Result<ReservedChar, Self::Error> {
95 '\'' => Ok(ReservedChar::Quote),
96 '"'
=> Ok(ReservedChar
::DoubleQuote
),
97 '
,'
=> Ok(ReservedChar
::Comma
),
98 '
('
=> Ok(ReservedChar
::OpenParenthese
),
99 '
)'
=> Ok(ReservedChar
::CloseParenthese
),
100 '
{'
=> Ok(ReservedChar
::OpenCurlyBrace
),
101 '
}'
=> Ok(ReservedChar
::CloseCurlyBrace
),
102 '
['
=> Ok(ReservedChar
::OpenBracket
),
103 '
]'
=> Ok(ReservedChar
::CloseBracket
),
104 '
:'
=> Ok(ReservedChar
::Colon
),
105 '
;'
=> Ok(ReservedChar
::SemiColon
),
106 '
/'
=> Ok(ReservedChar
::Slash
),
107 '
*'
=> Ok(ReservedChar
::Star
),
108 '
+'
=> Ok(ReservedChar
::Plus
),
109 '
='
=> Ok(ReservedChar
::EqualSign
),
110 ' '
=> Ok(ReservedChar
::Space
),
111 '
\t'
=> Ok(ReservedChar
::Tab
),
113 '
\r'
=> Ok(ReservedChar
::Backline
),
114 '
>'
=> Ok(ReservedChar
::SuperiorThan
),
115 '
|'
=> Ok(ReservedChar
::Pipe
),
116 '
~'
=> Ok(ReservedChar
::Tilde
),
117 '$'
=> Ok(ReservedChar
::Dollar
),
118 '
^' => Ok(ReservedChar
::Circumflex
),
119 _
=> Err("Unknown reserved char"),
125 fn is_useless(&self) -> bool
{
126 *self == ReservedChar
::Space
||
127 *self == ReservedChar
::Tab
||
128 *self == ReservedChar
::Backline
131 fn is_operator(&self) -> bool
{
132 Operator
::try_from(*self).is_ok()
136 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
145 impl fmt
::Display
for Operator
{
146 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
149 Operator
::Plus
=> '
+'
,
150 Operator
::Multiply
=> '
*'
,
151 Operator
::Minus
=> '
-'
,
152 Operator
::Modulo
=> '
%'
,
153 Operator
::Divide
=> '
/'
,
158 impl MyTryFrom
<char> for Operator
{
159 type Error
= &'
static str;
161 fn try_from(value
: char) -> Result
<Operator
, Self::Error
> {
163 '
+'
=> Ok(Operator
::Plus
),
164 '
*'
=> Ok(Operator
::Multiply
),
165 '
-'
=> Ok(Operator
::Minus
),
166 '
%'
=> Ok(Operator
::Modulo
),
167 '
/'
=> Ok(Operator
::Divide
),
168 _
=> Err("Unknown operator"),
173 impl MyTryFrom
<ReservedChar
> for Operator
{
174 type Error
= &'
static str;
176 fn try_from(value
: ReservedChar
) -> Result
<Operator
, Self::Error
> {
178 ReservedChar
::Slash
=> Ok(Operator
::Divide
),
179 ReservedChar
::Star
=> Ok(Operator
::Multiply
),
180 ReservedChar
::Plus
=> Ok(Operator
::Plus
),
181 _
=> Err("Unknown operator"),
186 #[derive(Eq, PartialEq, Clone, Debug)]
187 pub enum SelectorElement
<'a
> {
188 PseudoClass(&'a
str),
195 impl<'a
> MyTryFrom
<&'a
str> for SelectorElement
<'a
> {
196 type Error
= &'
static str;
198 fn try_from(value
: &'a
str) -> Result
<SelectorElement
, Self::Error
> {
199 if value
.starts_with('
.'
) {
201 Ok(SelectorElement
::Class(&value
[1..]))
203 Err("cannot determine selector")
205 } else if value
.starts_with('
#') {
207 Ok(SelectorElement
::Id(&value
[1..]))
209 Err("cannot determine selector")
211 } else if value
.starts_with('@'
) {
213 Ok(SelectorElement
::Media(&value
[1..]))
215 Err("cannot determine selector")
217 } else if value
.starts_with('
:'
) {
219 Ok(SelectorElement
::PseudoClass(&value
[1..]))
221 Err("cannot determine selector")
223 } else if value
.chars().next().unwrap_or(' '
).is_alphabetic() {
224 Ok(SelectorElement
::Tag(value
))
226 Err("unknown selector")
231 impl<'a
> fmt
::Display
for SelectorElement
<'a
> {
232 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
234 SelectorElement
::Class(c
) => write
!(f
, ".{}", c
),
235 SelectorElement
::Id(i
) => write
!(f
, "#{}", i
),
236 SelectorElement
::Tag(t
) => write
!(f
, "{}", t
),
237 SelectorElement
::Media(m
) => write
!(f
, "@{} ", m
),
238 SelectorElement
::PseudoClass(pc
) => write
!(f
, ":{}", pc
),
243 #[derive(Eq, PartialEq, Clone, Debug, Copy)]
244 pub enum SelectorOperator
{
248 EqualsOrStartsWithFollowedByDash
,
257 impl fmt
::Display
for SelectorOperator
{
258 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
260 SelectorOperator
::OneAttributeEquals
=> write
!(f
, "{}", "~="),
261 SelectorOperator
::EqualsOrStartsWithFollowedByDash
=> write
!(f
, "{}", "|="),
262 SelectorOperator
::EndsWith
=> write
!(f
, "{}", "$="),
263 SelectorOperator
::FirstStartsWith
=> write
!(f
, "{}", "^="),
264 SelectorOperator
::Contains
=> write
!(f
, "{}", "*="),
269 #[derive(Eq, PartialEq, Clone, Debug)]
273 /// Comment starting with `/**`.
277 SelectorElement(SelectorElement
<'a
>),
279 SelectorOperator(SelectorOperator
),
283 impl<'a
> fmt
::Display
for Token
<'a
> {
284 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
286 // Token::AtRule(at_rule) => write!(f, "{}", at_rule, content),
287 // Token::ElementRule(selectors) => write!(f, "{}", x),
288 Token
::Comment(c
) => write
!(f
, "{}", c
),
289 Token
::License(l
) => writeln
!(f
, "/*!{}*/", l
),
290 Token
::Char(c
) => write
!(f
, "{}", c
),
291 Token
::Other(s
) => write
!(f
, "{}", s
),
292 Token
::SelectorElement(ref se
) => write
!(f
, "{}", se
),
293 Token
::String(s
) => write
!(f
, "{}", s
),
294 Token
::SelectorOperator(so
) => write
!(f
, "{}", so
),
295 Token
::Operator(op
) => write
!(f
, "{}", op
),
301 fn is_comment(&self) -> bool
{
303 Token
::Comment(_
) => true,
308 fn is_char(&self) -> bool
{
310 Token
::Char(_
) => true,
315 fn get_char(&self) -> Option
<ReservedChar
> {
317 Token
::Char(c
) => Some(c
),
322 fn is_useless(&self) -> bool
{
324 Token
::Char(c
) => c
.is_useless(),
329 fn is_media(&self, media
: &str) -> bool
{
331 Token
::SelectorElement(SelectorElement
::Media(s
)) => s
== media
,
336 fn is_a_media(&self) -> bool
{
338 Token
::SelectorElement(SelectorElement
::Media(_
)) => true,
343 fn is_a_license(&self) -> bool
{
345 Token
::License(_
) => true,
350 fn is_operator(&self) -> bool
{
352 Token
::Operator(_
) => true,
353 Token
::Char(c
) => c
.is_operator(),
359 impl<'a
> PartialEq
<ReservedChar
> for Token
<'a
> {
360 fn eq(&self, other
: &ReservedChar
) -> bool
{
362 Token
::Char(c
) => c
== *other
,
368 fn get_line_comment
<'a
>(source
: &'a
str, iterator
: &mut Peekable
<CharIndices
>,
369 start_pos
: &mut usize) -> Option
<Token
<'a
>> {
371 while let Some((pos
, c
)) = iterator
.next() {
372 if let Ok(c
) = ReservedChar
::try_from(c
) {
373 if c
== ReservedChar
::Backline
{
374 let ret
= Some(Token
::Comment(&source
[*start_pos
..pos
]));
383 fn get_comment
<'a
>(source
: &'a
str, iterator
: &mut Peekable
<CharIndices
>,
384 start_pos
: &mut usize) -> Option
<Token
<'a
>> {
385 let mut prev
= ReservedChar
::Quote
;
387 let builder
= if let Some((_
, c
)) = iterator
.next() {
388 if c
== '
!'
|| c
== '
*'
{
392 if let Ok(c
) = ReservedChar
::try_from(c
) {
401 while let Some((pos
, c
)) = iterator
.next() {
402 if let Ok(c
) = ReservedChar
::try_from(c
) {
403 if c
== ReservedChar
::Slash
&& prev
== ReservedChar
::Star
{
404 let ret
= Some(builder(&source
[*start_pos
..pos
- 1]));
410 prev
= ReservedChar
::Space
;
416 fn get_string
<'a
>(source
: &'a
str, iterator
: &mut Peekable
<CharIndices
>, start_pos
: &mut usize,
417 start
: ReservedChar
) -> Option
<Token
<'a
>> {
418 while let Some((pos
, c
)) = iterator
.next() {
420 // we skip next character
424 if let Ok(c
) = ReservedChar
::try_from(c
) {
426 let ret
= Some(Token
::String(&source
[*start_pos
..pos
+ 1]));
435 fn fill_other
<'a
>(source
: &'a
str, v
: &mut Vec
<Token
<'a
>>, start
: usize, pos
: usize,
436 is_in_block
: isize, is_in_media
: bool
, is_in_attribute_selector
: bool
) {
438 if is_in_attribute_selector
== false &&
439 ((is_in_block
== 0 && is_in_media
== false) ||
440 (is_in_media
== true && is_in_block
== 1)) {
441 let mut is_pseudo_class
= false;
443 if let Some(&Token
::Char(ReservedChar
::Colon
)) = v
.last() {
444 is_pseudo_class
= true;
447 if let Ok(s
) = SelectorElement
::try_from(&source
[start
- add
..pos
]) {
451 v
.push(Token
::SelectorElement(s
));
453 let s
= &source
[start
..pos
];
454 if !s
.starts_with('
:'
) && !s
.starts_with('
.'
) && !s
.starts_with('
#') &&
455 !s
.starts_with('@'
) {
456 v
.push(Token
::Other(s
));
460 v
.push(Token
::Other(&source
[start
..pos
]));
465 pub fn tokenize
<'a
>(source
: &'a
str) -> Result
<Tokens
<'a
>, &'
static str> {
466 let mut v
= Vec
::with_capacity(1000);
467 let mut iterator
= source
.char_indices().peekable();
469 let mut is_in_block
: isize = 0;
470 let mut is_in_media
= false;
471 let mut is_in_attribute_selector
= false;
474 let (mut pos
, c
) = match iterator
.next() {
477 fill_other(source
, &mut v
, start
, source
.len(), is_in_block
, is_in_media
,
478 is_in_attribute_selector
);
482 if let Ok(c
) = ReservedChar
::try_from(c
) {
483 fill_other(source
, &mut v
, start
, pos
, is_in_block
, is_in_media
,
484 is_in_attribute_selector
);
485 is_in_media
= is_in_media
|| v
.last()
486 .unwrap_or(&Token
::Char(ReservedChar
::Space
))
489 c
== ReservedChar
::Quote
|| c
== ReservedChar
::DoubleQuote
=> {
490 if let Some(s
) = get_string(source
, &mut iterator
, &mut pos
, c
) {
494 c
== ReservedChar
::Star
&&
495 *v
.last().unwrap_or(&Token
::Char(ReservedChar
::Space
)) == ReservedChar
::Slash
=> {
497 if let Some(s
) = get_comment(source
, &mut iterator
, &mut pos
) {
501 c
== ReservedChar
::Slash
&&
502 *v
.last().unwrap_or(&Token
::Char(ReservedChar
::Space
)) == ReservedChar
::Slash
=> {
504 if let Some(s
) = get_line_comment(source
, &mut iterator
, &mut pos
) {
508 c
== ReservedChar
::OpenBracket
=> {
509 if is_in_attribute_selector
{
510 return Err("Already in attribute selector");
512 is_in_attribute_selector
= true;
513 v
.push(Token
::Char(c
));
515 c
== ReservedChar
::CloseBracket
=> {
516 if !is_in_attribute_selector
{
517 return Err("Unexpected ']'");
519 is_in_attribute_selector
= false;
520 v
.push(Token
::Char(c
));
522 c
== ReservedChar
::OpenCurlyBrace
=> {
524 v
.push(Token
::Char(c
));
526 c
== ReservedChar
::CloseCurlyBrace
=> {
529 return Err("Too much '}'");
530 } else if is_in_block
== 0 {
533 v
.push(Token
::Char(c
));
535 c
== ReservedChar
::EqualSign
=> {
537 .unwrap_or(&Token
::Char(ReservedChar
::Space
))
539 .unwrap_or(ReservedChar
::Space
) {
540 ReservedChar
::Tilde
=> Some(SelectorOperator
::OneAttributeEquals
),
541 ReservedChar
::Pipe
=> Some(SelectorOperator
::EqualsOrStartsWithFollowedByDash
),
542 ReservedChar
::Dollar
=> Some(SelectorOperator
::EndsWith
),
543 ReservedChar
::Circumflex
=> Some(SelectorOperator
::FirstStartsWith
),
544 ReservedChar
::Star
=> Some(SelectorOperator
::Contains
),
549 v
.push(Token
::SelectorOperator(r
));
551 None
=> v
.push(Token
::Char(c
)),
555 v
.push(Token
::Char(c
));
557 !v
.last().unwrap_or(&Token
::Char(ReservedChar
::Space
)).is_useless() &&
558 (!v
.last().unwrap_or(&Token
::Char(ReservedChar
::OpenCurlyBrace
)).is_char() ||
559 v
.last().unwrap_or(&Token
::Char(ReservedChar
::OpenCurlyBrace
)).is_operator() ||
560 v
.last().unwrap_or(&Token
::Char(ReservedChar
::OpenCurlyBrace
))
561 .get_char() == Some(ReservedChar
::CloseParenthese
)) => {
562 v
.push(Token
::Char(ReservedChar
::Space
));
564 let Ok(op
) = Operator
::try_from(c
) => {
565 v
.push(Token
::Operator(op
));
571 Ok(Tokens(clean_tokens(v
)))
574 fn clean_tokens
<'a
>(mut v
: Vec
<Token
<'a
>>) -> Vec
<Token
<'a
>> {
576 let mut is_in_calc
= false;
580 if v
[i
] == Token
::Other("calc") {
582 } else if is_in_calc
== true {
583 if v
[i
] == Token
::Char(ReservedChar
::CloseParenthese
) {
585 is_in_calc
= paren
!= 0;
586 } else if v
[i
] == Token
::Char(ReservedChar
::OpenParenthese
) {
590 if v
[i
].is_useless() {
591 if is_in_calc
== false &&
592 ((i
> 0 && ((v
[i
- 1].is_char() &&
593 v
[i
- 1] != Token
::Char(ReservedChar
::CloseParenthese
)) ||
594 v
[i
- 1].is_a_media() ||
595 v
[i
- 1].is_a_license())) ||
596 (i
< v
.len() - 1 && v
[i
+ 1].is_char())) {
599 } else if is_in_calc
== true && v
[i
- 1].is_useless() {
603 } else if v
[i
].is_comment() {
612 #[derive(Debug, PartialEq, Eq, Clone)]
613 pub struct Tokens
<'a
>(pub Vec
<Token
<'a
>>);
615 impl<'a
> fmt
::Display
for Tokens
<'a
> {
616 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
617 for token
in self.0.iter
() {
618 write
!(f
, "{}", token
)?
;
627 /*! just some license */
628 .foo > #bar p:hover {
634 @media screen and (max-width: 640px) {
639 let expected
= vec
![Token
::License(" just some license "),
640 Token
::SelectorElement(SelectorElement
::Class("foo")),
641 Token
::Char(ReservedChar
::SuperiorThan
),
642 Token
::SelectorElement(SelectorElement
::Id("bar")),
643 Token
::Char(ReservedChar
::Space
),
644 Token
::SelectorElement(SelectorElement
::Tag("p")),
645 Token
::SelectorElement(SelectorElement
::PseudoClass("hover")),
646 Token
::Char(ReservedChar
::OpenCurlyBrace
),
647 Token
::Other("color"),
648 Token
::Char(ReservedChar
::Colon
),
649 Token
::Other("blue"),
650 Token
::Char(ReservedChar
::SemiColon
),
651 Token
::Other("background"),
652 Token
::Char(ReservedChar
::Colon
),
653 Token
::String("\"blue\""),
654 Token
::Char(ReservedChar
::SemiColon
),
655 Token
::Char(ReservedChar
::CloseCurlyBrace
),
656 Token
::SelectorElement(SelectorElement
::Media("media")),
657 Token
::Other("screen"),
658 Token
::Char(ReservedChar
::Space
),
660 Token
::Char(ReservedChar
::OpenParenthese
),
661 Token
::Other("max-width"),
662 Token
::Char(ReservedChar
::Colon
),
663 Token
::Other("640px"),
664 Token
::Char(ReservedChar
::CloseParenthese
),
665 Token
::Char(ReservedChar
::OpenCurlyBrace
),
666 Token
::SelectorElement(SelectorElement
::Class("block")),
667 Token
::SelectorElement(SelectorElement
::PseudoClass("hover")),
668 Token
::Char(ReservedChar
::OpenCurlyBrace
),
669 Token
::Other("display"),
670 Token
::Char(ReservedChar
::Colon
),
671 Token
::Other("block"),
672 Token
::Char(ReservedChar
::SemiColon
),
673 Token
::Char(ReservedChar
::CloseCurlyBrace
),
674 Token
::Char(ReservedChar
::CloseCurlyBrace
)];
675 assert_eq
!(tokenize(s
), Ok(Tokens(expected
)));
681 /** just some license */
692 background-color: gold;
698 font-size: 12em; // I love big fonts
701 let expected
= vec
![Token
::License(" just some license "),
702 Token
::SelectorElement(SelectorElement
::Tag("a")),
703 Token
::Char(ReservedChar
::OpenBracket
),
704 Token
::Other("href"),
705 Token
::SelectorOperator(SelectorOperator
::Contains
),
706 Token
::String("\"example\""),
707 Token
::Char(ReservedChar
::CloseBracket
),
708 Token
::Char(ReservedChar
::OpenCurlyBrace
),
709 Token
::Other("background"),
710 Token
::Char(ReservedChar
::Colon
),
711 Token
::Other("yellow"),
712 Token
::Char(ReservedChar
::SemiColon
),
713 Token
::Char(ReservedChar
::CloseCurlyBrace
),
715 Token
::SelectorElement(SelectorElement
::Tag("a")),
716 Token
::Char(ReservedChar
::OpenBracket
),
717 Token
::Other("href"),
718 Token
::SelectorOperator(SelectorOperator
::EndsWith
),
719 Token
::String("\".org\""),
720 Token
::Char(ReservedChar
::CloseBracket
),
721 Token
::Char(ReservedChar
::OpenCurlyBrace
),
722 Token
::Other("font-style"),
723 Token
::Char(ReservedChar
::Colon
),
724 Token
::Other("italic"),
725 Token
::Char(ReservedChar
::SemiColon
),
726 Token
::Char(ReservedChar
::CloseCurlyBrace
),
728 Token
::SelectorElement(SelectorElement
::Tag("span")),
729 Token
::Char(ReservedChar
::OpenBracket
),
730 Token
::Other("lang"),
731 Token
::SelectorOperator(SelectorOperator
::EqualsOrStartsWithFollowedByDash
),
732 Token
::String("\"zh\""),
733 Token
::Char(ReservedChar
::CloseBracket
),
734 Token
::Char(ReservedChar
::OpenCurlyBrace
),
735 Token
::Other("color"),
736 Token
::Char(ReservedChar
::Colon
),
738 Token
::Char(ReservedChar
::SemiColon
),
739 Token
::Char(ReservedChar
::CloseCurlyBrace
),
741 Token
::SelectorElement(SelectorElement
::Tag("a")),
742 Token
::Char(ReservedChar
::OpenBracket
),
743 Token
::Other("href"),
744 Token
::SelectorOperator(SelectorOperator
::FirstStartsWith
),
745 Token
::String("\"/\""),
746 Token
::Char(ReservedChar
::CloseBracket
),
747 Token
::Char(ReservedChar
::OpenCurlyBrace
),
748 Token
::Other("background-color"),
749 Token
::Char(ReservedChar
::Colon
),
750 Token
::Other("gold"),
751 Token
::Char(ReservedChar
::SemiColon
),
752 Token
::Char(ReservedChar
::CloseCurlyBrace
),
754 Token
::SelectorElement(SelectorElement
::Tag("div")),
755 Token
::Char(ReservedChar
::OpenBracket
),
756 Token
::Other("value"),
757 Token
::SelectorOperator(SelectorOperator
::OneAttributeEquals
),
758 Token
::String("\"test\""),
759 Token
::Char(ReservedChar
::CloseBracket
),
760 Token
::Char(ReservedChar
::OpenCurlyBrace
),
761 Token
::Other("border-width"),
762 Token
::Char(ReservedChar
::Colon
),
764 Token
::Char(ReservedChar
::SemiColon
),
765 Token
::Char(ReservedChar
::CloseCurlyBrace
),
767 Token
::SelectorElement(SelectorElement
::Tag("span")),
768 Token
::Char(ReservedChar
::OpenBracket
),
769 Token
::Other("lang"),
770 Token
::Char(ReservedChar
::EqualSign
),
771 Token
::String("\"pt\""),
772 Token
::Char(ReservedChar
::CloseBracket
),
773 Token
::Char(ReservedChar
::OpenCurlyBrace
),
774 Token
::Other("font-size"),
775 Token
::Char(ReservedChar
::Colon
),
776 Token
::Other("12em"),
777 Token
::Char(ReservedChar
::SemiColon
),
778 Token
::Char(ReservedChar
::CloseCurlyBrace
)];
779 assert_eq
!(tokenize(s
), Ok(Tokens(expected
)));
784 let s
= "@media (max-width: 700px) { color: red; }";
786 let expected
= vec
![Token
::SelectorElement(SelectorElement
::Media("media")),
787 Token
::Char(ReservedChar
::OpenParenthese
),
788 Token
::Other("max-width"),
789 Token
::Char(ReservedChar
::Colon
),
790 Token
::Other("700px"),
791 Token
::Char(ReservedChar
::CloseParenthese
),
792 Token
::Char(ReservedChar
::OpenCurlyBrace
),
793 Token
::SelectorElement(SelectorElement
::Tag("color")),
794 Token
::Char(ReservedChar
::Colon
),
796 Token
::Char(ReservedChar
::SemiColon
),
797 Token
::Char(ReservedChar
::CloseCurlyBrace
)];
799 assert_eq
!(tokenize(s
), Ok(Tokens(expected
)));
804 let s
= ".foo { width: calc(100% - 34px); }";
806 let expected
= vec
![Token
::SelectorElement(SelectorElement
::Class("foo")),
807 Token
::Char(ReservedChar
::OpenCurlyBrace
),
808 Token
::Other("width"),
809 Token
::Char(ReservedChar
::Colon
),
810 Token
::Other("calc"),
811 Token
::Char(ReservedChar
::OpenParenthese
),
812 Token
::Other("100%"),
813 Token
::Char(ReservedChar
::Space
),
815 Token
::Char(ReservedChar
::Space
),
816 Token
::Other("34px"),
817 Token
::Char(ReservedChar
::CloseParenthese
),
818 Token
::Char(ReservedChar
::SemiColon
),
819 Token
::Char(ReservedChar
::CloseCurlyBrace
)];
820 assert_eq
!(tokenize(s
), Ok(Tokens(expected
)));