1use crate::{
8 CssParseError,
9 css_types::{Length, calc::Calc},
10 token_parser::{TokenParser, tokens},
11 token_types::SimpleToken,
12};
13use rustc_hash::FxHashMap;
14use std::fmt::{self, Display};
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct Fraction {
19 pub numerator: i32,
20 pub denominator: i32,
21}
22
23impl Display for Fraction {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "{} / {}", self.numerator, self.denominator)
27 }
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum WordRule {
33 Color,
34 Monochrome,
35 Grid,
36 ColorIndex,
37}
38
39impl Display for WordRule {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 WordRule::Color => write!(f, "color"),
43 WordRule::Monochrome => write!(f, "monochrome"),
44 WordRule::Grid => write!(f, "grid"),
45 WordRule::ColorIndex => write!(f, "color-index"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub enum MediaRuleValue {
53 Number(f32),
54 Length(Length),
55 String(String),
56 Fraction(Fraction),
57}
58
59impl Display for MediaRuleValue {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 MediaRuleValue::Number(n) => write!(f, "{}", n),
63 MediaRuleValue::Length(l) => write!(f, "{}", l),
64 MediaRuleValue::String(s) => write!(f, "{}", s),
65 MediaRuleValue::Fraction(frac) => write!(f, "{}", frac),
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub struct MediaKeyword {
73 pub r#type: String, pub key: String, pub not: bool,
76 pub only: bool, }
78
79impl MediaKeyword {
80 pub fn new(key: String, not: bool, only: bool) -> Self {
81 Self {
82 r#type: "media-keyword".to_string(),
83 key,
84 not,
85 only,
86 }
87 }
88}
89
90impl Display for MediaKeyword {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 let mut parts = Vec::new();
93
94 if self.not {
95 parts.push("not".to_string());
96 }
97
98 if self.only {
99 parts.push("only".to_string());
100 }
101
102 parts.push(self.key.clone());
103 write!(f, "{}", parts.join(" "))
104 }
105}
106
107#[derive(Debug, Clone, PartialEq)]
109pub struct MediaWordRule {
110 pub r#type: String, pub key_value: String, }
113
114impl MediaWordRule {
115 pub fn new(key_value: String) -> Self {
116 Self {
117 r#type: "word-rule".to_string(),
118 key_value,
119 }
120 }
121}
122
123impl Display for MediaWordRule {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "({})", self.key_value)
126 }
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub struct MediaRulePair {
132 #[allow(dead_code)]
133 pub r#type: String, pub key: String, pub value: MediaRuleValue, }
137
138impl MediaRulePair {
139 pub fn new(key: String, value: MediaRuleValue) -> Self {
140 Self {
141 r#type: "pair".to_string(),
142 key,
143 value,
144 }
145 }
146}
147
148impl Display for MediaRulePair {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "({}: {})", self.key, self.value)
151 }
152}
153
154#[derive(Debug, Clone, PartialEq)]
156pub struct MediaNotRule {
157 #[allow(dead_code)]
158 pub r#type: String, pub rule: Box<MediaQueryRule>, }
161
162impl MediaNotRule {
163 pub fn new(rule: MediaQueryRule) -> Self {
164 Self {
165 r#type: "not".to_string(),
166 rule: Box::new(rule),
167 }
168 }
169}
170
171impl Display for MediaNotRule {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self.rule.as_ref() {
174 MediaQueryRule::And(_) | MediaQueryRule::Or(_) => {
175 write!(f, "(not ({}))", self.rule)
176 },
177 _ => {
178 write!(f, "(not {})", self.rule)
179 },
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq)]
186pub struct MediaAndRules {
187 pub r#type: String, pub rules: Vec<MediaQueryRule>, }
190
191impl MediaAndRules {
192 pub fn new(rules: Vec<MediaQueryRule>) -> Self {
193 Self {
194 r#type: "and".to_string(),
195 rules,
196 }
197 }
198}
199
200impl Display for MediaAndRules {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 let rule_strings: Vec<String> = self.rules.iter().map(|rule| rule.to_string()).collect();
203 write!(f, "{}", rule_strings.join(" and "))
204 }
205}
206
207#[derive(Debug, Clone, PartialEq)]
209pub struct MediaOrRules {
210 pub r#type: String, pub rules: Vec<MediaQueryRule>, }
213
214impl MediaOrRules {
215 pub fn new(rules: Vec<MediaQueryRule>) -> Self {
216 Self {
217 r#type: "or".to_string(),
218 rules,
219 }
220 }
221}
222
223impl Display for MediaOrRules {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 let rule_strings: Vec<String> = self.rules.iter().map(|rule| rule.to_string()).collect();
226 write!(f, "{}", rule_strings.join(" or "))
227 }
228}
229
230#[derive(Debug, Clone, PartialEq)]
232#[allow(dead_code)]
233pub enum MediaQueryRule {
234 MediaKeyword(MediaKeyword),
235 WordRule(MediaWordRule),
236 Pair(MediaRulePair),
237 Not(MediaNotRule),
238 And(MediaAndRules),
239 Or(MediaOrRules),
240}
241
242#[derive(Debug, Clone, PartialEq)]
244pub struct MediaQuery {
245 pub queries: MediaQueryRule,
246}
247
248impl MediaQuery {
249 pub fn new(queries: MediaQueryRule) -> Self {
250 Self {
251 queries: Self::normalize(queries),
252 }
253 }
254
255 pub fn new_from_rule(rule: MediaQueryRule) -> Self {
256 Self::new(rule)
257 }
258
259 pub fn normalize(rule: MediaQueryRule) -> MediaQueryRule {
260 match rule {
261 MediaQueryRule::And(ref and_rules) => {
262 let mut flattened: Vec<MediaQueryRule> = Vec::new();
263 for r in &and_rules.rules {
264 let norm = Self::normalize(r.clone());
265 match norm {
266 MediaQueryRule::And(inner_and) => {
267 flattened.extend(inner_and.rules);
268 },
269 _ => {
270 flattened.push(norm);
271 },
272 }
273 }
274
275 if flattened.is_empty() {
276 return MediaQueryRule::MediaKeyword(MediaKeyword::new("all".to_string(), true, false));
277 }
278
279 let merged = merge_and_simplify_ranges(flattened);
280 if merged.is_empty() {
281 return MediaQueryRule::And(MediaAndRules::new(vec![MediaQueryRule::MediaKeyword(
282 MediaKeyword::new("all".to_string(), true, false),
283 )]));
284 }
285 MediaQueryRule::And(MediaAndRules::new(merged))
286 },
287 MediaQueryRule::Or(ref or_rules) => {
288 let normalized_rules: Vec<MediaQueryRule> = or_rules
289 .rules
290 .iter()
291 .map(|r| Self::normalize(r.clone()))
292 .collect();
293 MediaQueryRule::Or(MediaOrRules::new(normalized_rules))
294 },
295 MediaQueryRule::Not(ref not_rule) => {
296 let normalized_operand = Self::normalize(not_rule.rule.as_ref().clone());
297
298 match normalized_operand {
299 MediaQueryRule::MediaKeyword(ref keyword) if keyword.key == "all" && keyword.not => {
300 return MediaQueryRule::MediaKeyword(MediaKeyword::new(
301 "all".to_string(),
302 false,
303 false,
304 ));
305 },
306 MediaQueryRule::And(ref and_rules) if and_rules.rules.len() == 1 => {
307 if let MediaQueryRule::MediaKeyword(keyword) = &and_rules.rules[0]
308 && keyword.key == "all"
309 && keyword.not
310 {
311 return MediaQueryRule::MediaKeyword(MediaKeyword::new(
312 "all".to_string(),
313 false,
314 false,
315 ));
316 }
317 },
318 MediaQueryRule::Not(inner_not) => {
319 return Self::normalize(inner_not.rule.as_ref().clone());
320 },
321 _ => {},
322 }
323
324 MediaQueryRule::Not(MediaNotRule::new(normalized_operand))
325 },
326 _ => rule,
327 }
328 }
329}
330
331impl Display for MediaQueryRule {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 write!(f, "{}", MediaQuery::format_queries(self, false))
336 }
337}
338
339impl Display for MediaQuery {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 write!(
342 f,
343 "@media {}",
344 MediaQuery::format_queries(&self.queries, true)
345 )
346 }
347}
348
349impl MediaQuery {
350 fn format_queries(queries: &MediaQueryRule, is_top_level: bool) -> String {
351 match queries {
352 MediaQueryRule::MediaKeyword(keyword) => {
353 let prefix = if keyword.not {
354 "not "
355 } else if keyword.only {
356 "only "
357 } else {
358 ""
359 };
360 format!(
361 "{}{}",
362 prefix,
363 if is_top_level {
364 keyword.key.clone()
365 } else {
366 format!("({})", keyword.key)
367 }
368 )
369 },
370 MediaQueryRule::WordRule(word_rule) => {
371 format!("({})", word_rule.key_value)
372 },
373 MediaQueryRule::Pair(pair) => match &pair.value {
374 MediaRuleValue::Fraction(frac) => {
375 format!("({}: {} / {})", pair.key, frac.numerator, frac.denominator)
376 },
377 MediaRuleValue::Length(len) => {
378 format!("({}: {})", pair.key, len)
379 },
380 MediaRuleValue::String(s) => {
381 format!("({}: {})", pair.key, s)
382 },
383 MediaRuleValue::Number(n) => {
384 format!("({}: {})", pair.key, n)
385 },
386 },
387 MediaQueryRule::Not(not_rule) => match not_rule.rule.as_ref() {
388 MediaQueryRule::And(_) | MediaQueryRule::Or(_) | MediaQueryRule::Not(_) => {
389 format!(
390 "(not ({}))",
391 MediaQuery::format_queries(not_rule.rule.as_ref(), false)
392 )
393 },
394 _ => {
395 format!(
396 "(not {})",
397 MediaQuery::format_queries(not_rule.rule.as_ref(), false)
398 )
399 },
400 },
401 MediaQueryRule::And(and_rules) => {
402 let rule_strings: Vec<String> = and_rules
403 .rules
404 .iter()
405 .map(|rule| MediaQuery::format_queries(rule, false))
406 .collect();
407 rule_strings.join(" and ")
408 },
409 MediaQueryRule::Or(or_rules) => {
410 let valid_rules: Vec<&MediaQueryRule> = or_rules
412 .rules
413 .iter()
414 .filter(|r| !matches!(r, MediaQueryRule::Or(or) if or.rules.is_empty()))
415 .collect();
416
417 if valid_rules.is_empty() {
418 return "not all".to_string();
419 }
420
421 if valid_rules.len() == 1 {
422 return MediaQuery::format_queries(valid_rules[0], is_top_level);
423 }
424
425 let formatted_rules: Vec<String> = valid_rules
426 .iter()
427 .map(|rule| match rule {
428 MediaQueryRule::And(_) | MediaQueryRule::Or(_) => {
429 let rule_string = MediaQuery::format_queries(rule, false);
430 if !is_top_level {
431 format!("({})", rule_string)
432 } else {
433 rule_string
434 }
435 },
436 _ => MediaQuery::format_queries(rule, false),
437 })
438 .collect();
439
440 if is_top_level {
441 formatted_rules.join(", ")
442 } else {
443 formatted_rules.join(" or ")
444 }
445 },
446 }
447 }
448}
449
450impl MediaQuery {
451 pub fn parser() -> TokenParser<MediaQuery> {
452 TokenParser::new(
453 |tokens| {
454 if let Ok(Some(SimpleToken::AtKeyword(keyword))) = tokens.peek() {
455 if keyword == "media" {
456 tokens.consume_next_token()?; if let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
460 tokens.consume_next_token()?;
461 } else {
462 return Err(CssParseError::ParseError {
464 message: "Expected whitespace or content after @media".to_string(),
465 });
466 }
467 } else {
468 return Err(CssParseError::ParseError {
469 message: "Expected @media at-keyword".to_string(),
470 });
471 }
472 } else {
473 }
476
477 let rule = (media_query_rule_parser().run)(tokens)?;
478 Ok(MediaQuery::new_from_rule(rule))
479 },
480 "media_query_parser",
481 )
482 }
483
484 pub fn has_balanced_parens(input: &str) -> bool {
486 has_balanced_parens(input)
487 }
488}
489
490pub fn validate_media_query(input: &str) -> Result<MediaQuery, String> {
492 if !has_balanced_parens(input) {
493 return Err(crate::at_queries::messages::MediaQueryErrors::UNBALANCED_PARENS.to_string());
494 }
495
496 match MediaQuery::parser().parse_to_end(input) {
497 Ok(media_query) => Ok(media_query),
498 Err(_) => Err(crate::at_queries::messages::MediaQueryErrors::SYNTAX_ERROR.to_string()),
499 }
500}
501
502fn has_balanced_parens(input: &str) -> bool {
504 let mut count = 0;
505 for ch in input.chars() {
506 match ch {
507 '(' => count += 1,
508 ')' => {
509 count -= 1;
510 if count < 0 {
511 return false;
512 }
513 },
514 _ => {},
515 }
516 }
517 count == 0
518}
519
520fn is_numeric_length(val: &MediaRuleValue) -> bool {
521 matches!(val, MediaRuleValue::Length(_))
522}
523
524fn merge_and_simplify_ranges(rules: Vec<MediaQueryRule>) -> Vec<MediaQueryRule> {
525 match merge_intervals_for_and(rules.clone()) {
526 Ok(merged) => {
527 if merged.is_empty() {
528 Vec::new()
530 } else {
531 merged
532 }
533 },
534 Err(_) => rules, }
536}
537
538fn merge_intervals_for_and(rules: Vec<MediaQueryRule>) -> Result<Vec<MediaQueryRule>, String> {
539 const EPSILON: f32 = 0.01;
540 let dimensions = ["width", "height"];
541
542 let mut intervals: FxHashMap<&str, Vec<(f32, f32)>> = FxHashMap::default();
544 intervals.insert("width", Vec::new());
545 intervals.insert("height", Vec::new());
546
547 let mut units: FxHashMap<&str, String> = FxHashMap::default();
549 let mut has_any_unit_conflicts = false;
550
551 for rule in &rules {
553 if let MediaQueryRule::Not(not_rule) = rule
554 && let MediaQueryRule::And(and_rules) = not_rule.rule.as_ref()
555 && and_rules.rules.len() == 2
556 {
557 let left = &and_rules.rules[0];
558 let right = &and_rules.rules[1];
559
560 let mut left_branch_rules: Vec<MediaQueryRule> = rules
562 .iter()
563 .filter(|r| !std::ptr::eq(*r, rule))
564 .cloned()
565 .collect();
566 left_branch_rules.push(MediaQueryRule::Not(MediaNotRule::new(left.clone())));
567
568 let mut right_branch_rules: Vec<MediaQueryRule> = rules
570 .iter()
571 .filter(|r| !std::ptr::eq(*r, rule))
572 .cloned()
573 .collect();
574 right_branch_rules.push(MediaQueryRule::Not(MediaNotRule::new(right.clone())));
575
576 let left_branch = merge_intervals_for_and(left_branch_rules);
578 let right_branch = merge_intervals_for_and(right_branch_rules);
579
580 let mut or_rules = Vec::new();
581
582 if let Ok(left_result) = left_branch
584 && !left_result.is_empty()
585 {
586 if left_result.len() == 1 {
587 or_rules.push(left_result.into_iter().next().unwrap());
588 } else {
589 or_rules.push(MediaQueryRule::And(MediaAndRules::new(left_result)));
590 }
591 }
592
593 if let Ok(right_result) = right_branch
595 && !right_result.is_empty()
596 {
597 if right_result.len() == 1 {
598 or_rules.push(right_result.into_iter().next().unwrap());
599 } else {
600 or_rules.push(MediaQueryRule::And(MediaAndRules::new(right_result)));
601 }
602 }
603
604 if !or_rules.is_empty() {
605 return Ok(vec![MediaQueryRule::Or(MediaOrRules::new(or_rules))]);
606 }
607 }
608 }
609
610 for rule in &rules {
611 for dim in &dimensions {
612 match &rule {
613 MediaQueryRule::Pair(pair)
615 if (pair.key == format!("min-{}", dim) || pair.key == format!("max-{}", dim))
616 && is_numeric_length(&pair.value) =>
617 {
618 if let MediaRuleValue::Length(length) = &pair.value {
619 let val = length.value;
620
621 let dim_intervals = intervals.get(dim).unwrap();
623 if dim_intervals.is_empty() {
624 units.insert(dim, length.unit.clone());
625 } else if units.get(dim) != Some(&length.unit) {
626 has_any_unit_conflicts = true;
627 }
628
629 let interval = if pair.key.starts_with("min-") {
630 (val, f32::INFINITY)
631 } else {
632 (f32::NEG_INFINITY, val)
633 };
634
635 intervals.get_mut(dim).unwrap().push(interval);
636 break;
637 }
638 },
639
640 MediaQueryRule::Not(not_rule) => {
642 if let MediaQueryRule::Pair(pair) = not_rule.rule.as_ref()
643 && (pair.key == format!("min-{}", dim) || pair.key == format!("max-{}", dim))
644 && is_numeric_length(&pair.value)
645 && let MediaRuleValue::Length(length) = &pair.value
646 {
647 let val = length.value;
648
649 let dim_intervals = intervals.get(dim).unwrap();
651 if dim_intervals.is_empty() {
652 units.insert(dim, length.unit.clone());
653 } else if units.get(dim) != Some(&length.unit) {
654 has_any_unit_conflicts = true;
655 }
656
657 let interval = if pair.key.starts_with("min-") {
659 (f32::NEG_INFINITY, val - EPSILON)
660 } else {
661 (val + EPSILON, f32::INFINITY)
662 };
663
664 intervals.get_mut(dim).unwrap().push(interval);
665 break;
666 }
667 },
668
669 _ => {},
670 }
671 }
672
673 if !matches!(
675 rule,
676 MediaQueryRule::Pair(pair) if (
677 pair.key == "min-width" ||
678 pair.key == "max-width" ||
679 pair.key == "min-height" ||
680 pair.key == "max-height"
681 ) && is_numeric_length(&pair.value)
682 ) && !matches!(
683 rule,
684 MediaQueryRule::Not(not_rule) if matches!(
685 not_rule.rule.as_ref(),
686 MediaQueryRule::Pair(pair) if (
687 pair.key == "min-width" ||
688 pair.key == "max-width" ||
689 pair.key == "min-height" ||
690 pair.key == "max-height"
691 ) && is_numeric_length(&pair.value)
692 )
693 ) {
694 return Ok(rules);
695 }
696 }
697
698 let mut result = Vec::new();
699
700 if has_any_unit_conflicts {
702 return Ok(rules);
703 }
704
705 for dim in &dimensions {
706 let dim_intervals = intervals.get(dim).unwrap();
707 if dim_intervals.is_empty() {
708 continue;
709 }
710
711 let mut lower = f32::NEG_INFINITY;
712 let mut upper = f32::INFINITY;
713 for (l, u) in dim_intervals {
714 if *l > lower {
715 lower = *l;
716 }
717 if *u < upper {
718 upper = *u;
719 }
720 }
721
722 if lower > upper {
723 return Ok(Vec::new());
724 }
725
726 if lower != f32::NEG_INFINITY && lower.is_finite() {
727 result.push(MediaQueryRule::Pair(MediaRulePair::new(
728 format!("min-{}", dim),
729 MediaRuleValue::Length(Length::new(lower, units.get(dim).unwrap().clone())),
730 )));
731 }
732
733 if upper != f32::INFINITY && upper.is_finite() {
734 result.push(MediaQueryRule::Pair(MediaRulePair::new(
735 format!("max-{}", dim),
736 MediaRuleValue::Length(Length::new(upper, units.get(dim).unwrap().clone())),
737 )));
738 }
739 }
740 Ok(if result.is_empty() { rules } else { result })
741}
742
743fn basic_media_type_parser() -> TokenParser<String> {
745 tokens::ident()
746 .map(
747 |token| {
748 if let SimpleToken::Ident(value) = token {
749 value
750 } else {
751 "all".to_string()
752 }
753 },
754 Some("extract_media_type"),
755 )
756 .where_fn(
757 |value| matches!(value.as_str(), "screen" | "print" | "all"),
758 Some("valid_media_type"),
759 )
760}
761
762fn media_keyword_parser() -> TokenParser<MediaQueryRule> {
764 TokenParser::new(
765 |tokens| {
766 let mut not_value = false;
767 let mut only_value = false; if let Ok(Some(SimpleToken::Ident(val))) = tokens.peek()
771 && val == "not"
772 {
773 tokens.consume_next_token()?; not_value = true;
775
776 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
778 tokens.consume_next_token()?;
779 }
780 }
781
782 if let Ok(Some(SimpleToken::Ident(val))) = tokens.peek()
784 && val == "only"
785 {
786 tokens.consume_next_token()?; only_value = true;
788
789 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
791 tokens.consume_next_token()?;
792 }
793 }
794
795 let media_type = (basic_media_type_parser().run)(tokens)?;
797
798 Ok(MediaQueryRule::MediaKeyword(MediaKeyword::new(
799 media_type, not_value, only_value,
800 )))
801 },
802 "media_keyword_parser",
803 )
804}
805
806fn media_word_rule_parser() -> TokenParser<MediaQueryRule> {
808 tokens::ident()
809 .map(
810 |token| {
811 if let SimpleToken::Ident(value) = token {
812 value
813 } else {
814 "color".to_string()
815 }
816 },
817 Some("extract_word_rule"),
818 )
819 .where_fn(
820 |value| {
821 matches!(
822 value.as_str(),
823 "color" | "monochrome" | "grid" | "color-index"
824 )
825 },
826 Some("valid_word_rule"),
827 )
828 .surrounded_by(
829 TokenParser::<SimpleToken>::token(SimpleToken::LeftParen, Some("OpenParen")),
830 Some(TokenParser::<SimpleToken>::token(
831 SimpleToken::RightParen,
832 Some("CloseParen"),
833 )),
834 )
835 .map(
836 |keyword| MediaQueryRule::WordRule(MediaWordRule::new(keyword)),
837 Some("create_word_rule"),
838 )
839}
840
841fn media_rule_value_parser() -> TokenParser<MediaRuleValue> {
842 TokenParser::one_of(vec![
843 Calc::parser().map(
844 |calc| MediaRuleValue::String(calc.to_string()),
845 Some("calc_to_string"),
846 ),
847 tokens::dimension().map(
849 |token| {
850 if let SimpleToken::Dimension { value, unit } = token {
851 MediaRuleValue::Length(Length::new(value as f32, unit))
852 } else {
853 MediaRuleValue::Number(0.0)
854 }
855 },
856 Some("dimension_to_length"),
857 ),
858 tokens::ident().map(
859 |token| {
860 if let SimpleToken::Ident(value) = token {
861 MediaRuleValue::String(value)
862 } else {
863 MediaRuleValue::String("".to_string())
864 }
865 },
866 Some("ident_to_string"),
867 ),
868 TokenParser::new(
870 |tokens| {
871 let first_num = if let Ok(Some(SimpleToken::Number(value))) = tokens.consume_next_token() {
873 value as i32
874 } else {
875 return Err(CssParseError::ParseError {
876 message: "Expected first number in fraction".to_string(),
877 });
878 };
879
880 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
882 tokens.consume_next_token()?;
883 }
884
885 if let Ok(Some(SimpleToken::Delim(ch))) = tokens.consume_next_token() {
887 if ch != '/' {
888 return Err(CssParseError::ParseError {
889 message: "Expected '/' in fraction".to_string(),
890 });
891 }
892 } else {
893 return Err(CssParseError::ParseError {
894 message: "Expected '/' delimiter".to_string(),
895 });
896 }
897
898 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
900 tokens.consume_next_token()?;
901 }
902
903 let second_num = if let Ok(Some(SimpleToken::Number(value))) = tokens.consume_next_token() {
905 value as i32
906 } else {
907 return Err(CssParseError::ParseError {
908 message: "Expected second number in fraction".to_string(),
909 });
910 };
911
912 Ok(MediaRuleValue::Fraction(Fraction {
913 numerator: first_num,
914 denominator: second_num,
915 }))
916 },
917 "fraction_parser",
918 ),
919 tokens::number().map(
921 |token| {
922 if let SimpleToken::Number(value) = token {
923 MediaRuleValue::Number(value as f32)
924 } else {
925 MediaRuleValue::Number(0.0)
926 }
927 },
928 Some("number_to_value"),
929 ),
930 ])
931}
932
933fn simple_pair_parser(value_parser: TokenParser<MediaRuleValue>) -> TokenParser<MediaQueryRule> {
935 let value_parser_rc = value_parser.run.clone(); TokenParser::new(
938 move |tokens| {
939 if let Ok(Some(SimpleToken::LeftParen)) = tokens.consume_next_token() {
941 } else {
943 return Err(CssParseError::ParseError {
944 message: "Expected opening parenthesis".to_string(),
945 });
946 }
947
948 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
950 tokens.consume_next_token()?;
951 }
952
953 let key = if let Ok(Some(SimpleToken::Ident(key_name))) = tokens.consume_next_token() {
955 key_name
956 } else {
957 return Err(CssParseError::ParseError {
958 message: "Expected media feature name".to_string(),
959 });
960 };
961
962 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
964 tokens.consume_next_token()?;
965 }
966
967 if let Ok(Some(SimpleToken::Colon)) = tokens.consume_next_token() {
969 } else {
971 return Err(CssParseError::ParseError {
972 message: "Expected colon after media feature name".to_string(),
973 });
974 }
975
976 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
978 tokens.consume_next_token()?;
979 }
980
981 let value = (value_parser_rc)(tokens)?;
983
984 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
986 tokens.consume_next_token()?;
987 }
988
989 if let Ok(Some(SimpleToken::RightParen)) = tokens.consume_next_token() {
991 } else {
993 return Err(CssParseError::ParseError {
994 message: "Expected closing parenthesis".to_string(),
995 });
996 }
997
998 Ok(MediaQueryRule::Pair(MediaRulePair::new(key, value)))
999 },
1000 "simple_pair_parser",
1001 )
1002}
1003
1004fn combined_inequality_parser() -> TokenParser<MediaQueryRule> {
1006 TokenParser::one_of(vec![
1007 media_inequality_rule_parser(), media_inequality_rule_parser_reversed(), ])
1010}
1011
1012fn media_inequality_rule_parser() -> TokenParser<MediaQueryRule> {
1014 TokenParser::new(
1015 |tokens| {
1016 let open_token = tokens
1018 .consume_next_token()?
1019 .ok_or(CssParseError::ParseError {
1020 message: "Expected opening parenthesis".to_string(),
1021 })?;
1022 if !matches!(open_token, SimpleToken::LeftParen) {
1023 return Err(CssParseError::ParseError {
1024 message: format!("Expected '(' token, got {:?}", open_token),
1025 });
1026 }
1027
1028 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1030 tokens.consume_next_token()?;
1031 }
1032
1033 let key_token = tokens
1035 .consume_next_token()?
1036 .ok_or(CssParseError::ParseError {
1037 message: "Expected property name".to_string(),
1038 })?;
1039 let key = if let SimpleToken::Ident(name) = key_token {
1040 if name == "width" || name == "height" {
1041 name
1042 } else {
1043 return Err(CssParseError::ParseError {
1044 message: format!("Expected 'width' or 'height', got '{}'", name),
1045 });
1046 }
1047 } else {
1048 return Err(CssParseError::ParseError {
1049 message: format!("Expected identifier, got {:?}", key_token),
1050 });
1051 };
1052
1053 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1055 tokens.consume_next_token()?;
1056 }
1057
1058 let op_token = tokens
1060 .consume_next_token()?
1061 .ok_or(CssParseError::ParseError {
1062 message: "Expected comparison operator".to_string(),
1063 })?;
1064 let op = if let SimpleToken::Delim(op_char) = op_token {
1065 if op_char == '<' || op_char == '>' {
1066 op_char
1067 } else {
1068 return Err(CssParseError::ParseError {
1069 message: format!("Expected '<' or '>', got '{}'", op_char),
1070 });
1071 }
1072 } else {
1073 return Err(CssParseError::ParseError {
1074 message: format!("Expected delimiter, got {:?}", op_token),
1075 });
1076 };
1077
1078 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1080 tokens.consume_next_token()?;
1081 }
1082
1083 let has_equals = if let Ok(Some(SimpleToken::Delim('='))) = tokens.peek() {
1085 tokens.consume_next_token()?;
1086 true
1087 } else {
1088 false
1089 };
1090
1091 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1093 tokens.consume_next_token()?;
1094 }
1095
1096 let dim_token = tokens
1098 .consume_next_token()?
1099 .ok_or(CssParseError::ParseError {
1100 message: "Expected dimension value".to_string(),
1101 })?;
1102 let mut dimension = if let SimpleToken::Dimension { value, unit } = dim_token {
1103 Length::new(value as f32, unit)
1104 } else {
1105 return Err(CssParseError::ParseError {
1106 message: format!("Expected dimension, got {:?}", dim_token),
1107 });
1108 };
1109
1110 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1112 tokens.consume_next_token()?;
1113 }
1114
1115 let close_token = tokens
1117 .consume_next_token()?
1118 .ok_or(CssParseError::ParseError {
1119 message: "Expected closing parenthesis".to_string(),
1120 })?;
1121 if !matches!(close_token, SimpleToken::RightParen) {
1122 return Err(CssParseError::ParseError {
1123 message: format!("Expected ')' token, got {:?}", close_token),
1124 });
1125 }
1126
1127 if !has_equals {
1128 const EPSILON: f32 = 0.01;
1129 if op == '>' {
1130 dimension.value += EPSILON;
1132 } else {
1133 dimension.value -= EPSILON;
1135 }
1136 }
1137
1138 let final_key = if op == '>' {
1140 format!("min-{}", key)
1141 } else {
1142 format!("max-{}", key)
1143 };
1144
1145 Ok(MediaQueryRule::Pair(MediaRulePair::new(
1146 final_key,
1147 MediaRuleValue::Length(dimension),
1148 )))
1149 },
1150 "media_inequality_rule_parser",
1151 )
1152}
1153
1154fn media_inequality_rule_parser_reversed() -> TokenParser<MediaQueryRule> {
1156 TokenParser::new(
1157 |tokens| {
1158 let open_token = tokens
1160 .consume_next_token()?
1161 .ok_or(CssParseError::ParseError {
1162 message: "Expected opening parenthesis".to_string(),
1163 })?;
1164 if !matches!(open_token, SimpleToken::LeftParen) {
1165 return Err(CssParseError::ParseError {
1166 message: format!("Expected '(' token, got {:?}", open_token),
1167 });
1168 }
1169
1170 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1172 tokens.consume_next_token()?;
1173 }
1174
1175 let dim_token = tokens
1177 .consume_next_token()?
1178 .ok_or(CssParseError::ParseError {
1179 message: "Expected dimension value".to_string(),
1180 })?;
1181 let dimension = if let SimpleToken::Dimension { value, unit } = dim_token {
1182 MediaRuleValue::Length(Length::new(value as f32, unit))
1183 } else {
1184 return Err(CssParseError::ParseError {
1185 message: format!("Expected dimension, got {:?}", dim_token),
1186 });
1187 };
1188
1189 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1191 tokens.consume_next_token()?;
1192 }
1193
1194 let op_token = tokens
1196 .consume_next_token()?
1197 .ok_or(CssParseError::ParseError {
1198 message: "Expected comparison operator".to_string(),
1199 })?;
1200 let op = if let SimpleToken::Delim(op_char) = op_token {
1201 if op_char == '<' || op_char == '>' {
1202 op_char
1203 } else {
1204 return Err(CssParseError::ParseError {
1205 message: format!("Expected '<' or '>', got '{}'", op_char),
1206 });
1207 }
1208 } else {
1209 return Err(CssParseError::ParseError {
1210 message: format!("Expected delimiter, got {:?}", op_token),
1211 });
1212 };
1213
1214 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1216 tokens.consume_next_token()?;
1217 }
1218
1219 let has_equals = if let Ok(Some(SimpleToken::Delim('='))) = tokens.peek() {
1221 tokens.consume_next_token()?;
1222 true
1223 } else {
1224 false
1225 };
1226
1227 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1229 tokens.consume_next_token()?;
1230 }
1231
1232 let key_token = tokens
1234 .consume_next_token()?
1235 .ok_or(CssParseError::ParseError {
1236 message: "Expected property name".to_string(),
1237 })?;
1238 let key = if let SimpleToken::Ident(name) = key_token {
1239 if name == "width" || name == "height" {
1240 name
1241 } else {
1242 return Err(CssParseError::ParseError {
1243 message: format!("Expected 'width' or 'height', got '{}'", name),
1244 });
1245 }
1246 } else {
1247 return Err(CssParseError::ParseError {
1248 message: format!("Expected identifier, got {:?}", key_token),
1249 });
1250 };
1251
1252 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1254 tokens.consume_next_token()?;
1255 }
1256
1257 let close_token = tokens
1259 .consume_next_token()?
1260 .ok_or(CssParseError::ParseError {
1261 message: "Expected closing parenthesis".to_string(),
1262 })?;
1263 if !matches!(close_token, SimpleToken::RightParen) {
1264 return Err(CssParseError::ParseError {
1265 message: format!("Expected ')' token, got {:?}", close_token),
1266 });
1267 }
1268
1269 let mut adjusted_dimension = dimension;
1270 if !has_equals {
1271 const EPSILON: f32 = 0.01;
1272 if let MediaRuleValue::Length(ref mut length) = adjusted_dimension {
1273 if op == '>' {
1274 length.value -= EPSILON;
1276 } else {
1277 length.value += EPSILON;
1279 }
1280 }
1281 }
1282
1283 let final_key = if op == '>' {
1285 format!("max-{}", key)
1286 } else {
1287 format!("min-{}", key)
1288 };
1289
1290 Ok(MediaQueryRule::Pair(MediaRulePair::new(
1291 final_key,
1292 adjusted_dimension,
1293 )))
1294 },
1295 "media_inequality_rule_parser_reversed",
1296 )
1297}
1298
1299fn double_inequality_rule_parser() -> TokenParser<MediaQueryRule> {
1301 TokenParser::new(
1302 |tokens| {
1303 let open_token = tokens
1305 .consume_next_token()?
1306 .ok_or(CssParseError::ParseError {
1307 message: "Expected opening parenthesis".to_string(),
1308 })?;
1309 if !matches!(open_token, SimpleToken::LeftParen) {
1310 return Err(CssParseError::ParseError {
1311 message: format!("Expected '(' token, got {:?}", open_token),
1312 });
1313 }
1314
1315 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1317 tokens.consume_next_token()?;
1318 }
1319
1320 let lower_dim_token = tokens
1322 .consume_next_token()?
1323 .ok_or(CssParseError::ParseError {
1324 message: "Expected lower bound dimension".to_string(),
1325 })?;
1326 let lower_dimension = if let SimpleToken::Dimension { value, unit } = lower_dim_token {
1327 MediaRuleValue::Length(Length::new(value as f32, unit))
1328 } else {
1329 return Err(CssParseError::ParseError {
1330 message: format!("Expected dimension, got {:?}", lower_dim_token),
1331 });
1332 };
1333
1334 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1336 tokens.consume_next_token()?;
1337 }
1338
1339 let op1_token = tokens
1341 .consume_next_token()?
1342 .ok_or(CssParseError::ParseError {
1343 message: "Expected first comparison operator".to_string(),
1344 })?;
1345 let _op1 = if let SimpleToken::Delim(op_char) = op1_token {
1346 if op_char == '<' || op_char == '>' {
1347 op_char
1348 } else {
1349 return Err(CssParseError::ParseError {
1350 message: format!("Expected '<' or '>', got '{}'", op_char),
1351 });
1352 }
1353 } else {
1354 return Err(CssParseError::ParseError {
1355 message: format!("Expected delimiter, got {:?}", op1_token),
1356 });
1357 };
1358
1359 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1361 tokens.consume_next_token()?;
1362 }
1363
1364 let _eq1 = if let Ok(Some(SimpleToken::Delim('='))) = tokens.peek() {
1366 tokens.consume_next_token()?;
1367 true
1368 } else {
1369 false
1370 };
1371
1372 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1374 tokens.consume_next_token()?;
1375 }
1376
1377 let key_token = tokens
1379 .consume_next_token()?
1380 .ok_or(CssParseError::ParseError {
1381 message: "Expected property name".to_string(),
1382 })?;
1383 let key = if let SimpleToken::Ident(name) = key_token {
1384 if name == "width" || name == "height" {
1385 name
1386 } else {
1387 return Err(CssParseError::ParseError {
1388 message: format!("Expected 'width' or 'height', got '{}'", name),
1389 });
1390 }
1391 } else {
1392 return Err(CssParseError::ParseError {
1393 message: format!("Expected identifier, got {:?}", key_token),
1394 });
1395 };
1396
1397 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1399 tokens.consume_next_token()?;
1400 }
1401
1402 let op2_token = tokens
1404 .consume_next_token()?
1405 .ok_or(CssParseError::ParseError {
1406 message: "Expected second comparison operator".to_string(),
1407 })?;
1408 let _op2 = if let SimpleToken::Delim(op_char) = op2_token {
1409 if op_char == '<' || op_char == '>' {
1410 op_char
1411 } else {
1412 return Err(CssParseError::ParseError {
1413 message: format!("Expected '<' or '>', got '{}'", op_char),
1414 });
1415 }
1416 } else {
1417 return Err(CssParseError::ParseError {
1418 message: format!("Expected delimiter, got {:?}", op2_token),
1419 });
1420 };
1421
1422 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1424 tokens.consume_next_token()?;
1425 }
1426
1427 let _eq2 = if let Ok(Some(SimpleToken::Delim('='))) = tokens.peek() {
1429 tokens.consume_next_token()?;
1430 true
1431 } else {
1432 false
1433 };
1434
1435 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1437 tokens.consume_next_token()?;
1438 }
1439
1440 let upper_dim_token = tokens
1442 .consume_next_token()?
1443 .ok_or(CssParseError::ParseError {
1444 message: "Expected upper bound dimension".to_string(),
1445 })?;
1446 let upper_dimension = if let SimpleToken::Dimension { value, unit } = upper_dim_token {
1447 MediaRuleValue::Length(Length::new(value as f32, unit))
1448 } else {
1449 return Err(CssParseError::ParseError {
1450 message: format!("Expected dimension, got {:?}", upper_dim_token),
1451 });
1452 };
1453
1454 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1456 tokens.consume_next_token()?;
1457 }
1458
1459 let close_token = tokens
1461 .consume_next_token()?
1462 .ok_or(CssParseError::ParseError {
1463 message: "Expected closing parenthesis".to_string(),
1464 })?;
1465 if !matches!(close_token, SimpleToken::RightParen) {
1466 return Err(CssParseError::ParseError {
1467 message: format!("Expected ')' token, got {:?}", close_token),
1468 });
1469 }
1470
1471 let min_key = format!("min-{}", key);
1474 let max_key = format!("max-{}", key);
1475
1476 const EPSILON: f32 = 0.01;
1478
1479 let (min_value, max_value) = if !_eq1 && !_eq2 {
1483 if _op1 == '>' {
1485 (upper_dimension, lower_dimension) } else {
1487 (lower_dimension, upper_dimension) }
1489 } else if !_eq1 {
1490 if _op1 == '>' {
1492 (upper_dimension, lower_dimension) } else {
1494 (lower_dimension, upper_dimension) }
1496 } else if !_eq2 {
1497 if _op2 == '>' {
1499 (upper_dimension, lower_dimension) } else {
1501 (lower_dimension, upper_dimension) }
1503 } else {
1504 if _op1 == '>' && _eq1 {
1506 (upper_dimension, lower_dimension) } else if _op1 == '<' && _eq1 {
1509 (lower_dimension, upper_dimension) } else {
1512 (lower_dimension, upper_dimension)
1514 }
1515 };
1516 let mut min_value = min_value;
1517 let mut max_value = max_value;
1518
1519 if let MediaRuleValue::Length(ref mut length) = min_value {
1521 if (_op1 == '<' && !_eq1) || (_op2 == '>' && !_eq2) {
1523 length.value += EPSILON; }
1525 }
1526 if let MediaRuleValue::Length(ref mut length) = max_value {
1527 if (_op1 == '>' && !_eq1) || (_op2 == '<' && !_eq2) {
1529 length.value -= EPSILON; }
1531 }
1532
1533 Ok(MediaQueryRule::And(MediaAndRules::new(vec![
1534 MediaQueryRule::Pair(MediaRulePair::new(min_key, min_value)),
1535 MediaQueryRule::Pair(MediaRulePair::new(max_key, max_value)),
1536 ])))
1537 },
1538 "double_inequality_rule_parser",
1539 )
1540}
1541
1542fn leading_not_parser() -> TokenParser<MediaQueryRule> {
1544 TokenParser::new(
1545 |tokens| {
1546 let not_token = tokens
1548 .consume_next_token()?
1549 .ok_or(CssParseError::ParseError {
1550 message: "Expected 'not' keyword".to_string(),
1551 })?;
1552 if let SimpleToken::Ident(keyword) = not_token {
1553 if keyword != "not" {
1554 return Err(CssParseError::ParseError {
1555 message: format!("Expected 'not', got '{}'", keyword),
1556 });
1557 }
1558 } else {
1559 return Err(CssParseError::ParseError {
1560 message: format!("Expected identifier, got {:?}", not_token),
1561 });
1562 }
1563
1564 let whitespace_token = tokens
1566 .consume_next_token()?
1567 .ok_or(CssParseError::ParseError {
1568 message: "Expected whitespace after 'not'".to_string(),
1569 })?;
1570 if !matches!(whitespace_token, SimpleToken::Whitespace) {
1571 return Err(CssParseError::ParseError {
1572 message: format!("Expected whitespace, got {:?}", whitespace_token),
1573 });
1574 }
1575
1576 let inner_rule = (normal_rule_parser().run)(tokens)?;
1578 Ok(MediaQueryRule::Not(MediaNotRule::new(inner_rule)))
1579 },
1580 "leading_not_parser",
1581 )
1582}
1583
1584fn parenthesized_not_parser() -> TokenParser<MediaQueryRule> {
1586 TokenParser::new(
1587 |tokens| {
1588 if let Ok(Some(SimpleToken::LeftParen)) = tokens.peek() {
1590 tokens.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1594 tokens.consume_next_token()?;
1595 }
1596
1597 if let Ok(Some(SimpleToken::Ident(keyword))) = tokens.peek() {
1599 if keyword == "not" {
1600 tokens.consume_next_token()?; if let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1604 tokens.consume_next_token()?;
1605 } else {
1606 return Err(CssParseError::ParseError {
1607 message: "Expected whitespace after 'not' in parenthesized expression".to_string(),
1608 });
1609 }
1610
1611 let inner_rule = (normal_rule_parser().run)(tokens)?;
1613
1614 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1616 tokens.consume_next_token()?;
1617 }
1618
1619 if let Ok(Some(SimpleToken::RightParen)) = tokens.peek() {
1621 tokens.consume_next_token()?; Ok(MediaQueryRule::Not(MediaNotRule::new(inner_rule)))
1623 } else {
1624 Err(CssParseError::ParseError {
1625 message: "Expected closing parenthesis after parenthesized NOT expression"
1626 .to_string(),
1627 })
1628 }
1629 } else {
1630 Err(CssParseError::ParseError {
1631 message: "Expected 'not' keyword in parenthesized NOT expression".to_string(),
1632 })
1633 }
1634 } else {
1635 Err(CssParseError::ParseError {
1636 message: "Expected 'not' keyword in parenthesized NOT expression".to_string(),
1637 })
1638 }
1639 } else {
1640 Err(CssParseError::ParseError {
1641 message: "Expected opening parenthesis for parenthesized NOT expression".to_string(),
1642 })
1643 }
1644 },
1645 "parenthesized_not_parser",
1646 )
1647}
1648
1649fn media_query_rule_parser() -> TokenParser<MediaQueryRule> {
1650 or_combinator_parser()
1652}
1653
1654fn or_combinator_parser() -> TokenParser<MediaQueryRule> {
1656 TokenParser::new(
1657 |tokens| {
1658 let mut rules = Vec::new();
1659
1660 let first_rule = (and_combinator_parser().run)(tokens)?;
1662 rules.push(first_rule);
1663
1664 loop {
1666 let checkpoint = tokens.save_position();
1667
1668 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1670 tokens.consume_next_token()?;
1671 }
1672
1673 if let Ok(Some(SimpleToken::Comma)) = tokens.peek() {
1675 tokens.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1679 tokens.consume_next_token()?;
1680 }
1681
1682 let rule = (and_combinator_parser().run)(tokens)?;
1683 rules.push(rule);
1684 continue;
1685 }
1686
1687 if let Ok(Some(SimpleToken::Ident(keyword))) = tokens.peek()
1689 && keyword == "or"
1690 {
1691 tokens.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1695 tokens.consume_next_token()?;
1696 }
1697
1698 let rule = (and_combinator_parser().run)(tokens)?;
1699 rules.push(rule);
1700 continue;
1701 }
1702
1703 tokens.restore_position(checkpoint)?;
1705 break;
1706 }
1707
1708 if rules.len() == 1 {
1710 Ok(rules.into_iter().next().unwrap())
1711 } else {
1712 Ok(MediaQueryRule::Or(MediaOrRules::new(rules)))
1713 }
1714 },
1715 "or_combinator_parser",
1716 )
1717}
1718
1719fn and_combinator_parser() -> TokenParser<MediaQueryRule> {
1721 TokenParser::new(
1722 |tokens| {
1723 let mut rules = Vec::new();
1724
1725 let first_rule = (normal_rule_parser().run)(tokens)?;
1727 rules.push(first_rule);
1728
1729 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1731 let checkpoint = tokens.save_position();
1733
1734 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1736 tokens.consume_next_token()?;
1737 }
1738
1739 if let Ok(Some(SimpleToken::Ident(keyword))) = tokens.peek() {
1741 if keyword == "and" {
1742 tokens.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1746 tokens.consume_next_token()?;
1747 }
1748
1749 let rule = (normal_rule_parser().run)(tokens)?;
1750 rules.push(rule);
1751 } else {
1752 tokens.restore_position(checkpoint)?;
1754 break;
1755 }
1756 } else {
1757 tokens.restore_position(checkpoint)?;
1759 break;
1760 }
1761 }
1762
1763 if rules.len() == 1 {
1765 Ok(rules.into_iter().next().unwrap())
1766 } else {
1767 Ok(MediaQueryRule::And(MediaAndRules::new(rules)))
1768 }
1769 },
1770 "and_combinator_parser",
1771 )
1772}
1773
1774fn normal_rule_parser() -> TokenParser<MediaQueryRule> {
1776 TokenParser::one_of(vec![
1777 media_keyword_parser(),
1780 parenthesized_not_parser(),
1782 leading_not_parser(),
1784 parenthesized_expression_parser(),
1786 double_inequality_rule_parser(),
1788 combined_inequality_parser(),
1790 media_word_rule_parser(),
1792 simple_pair_parser(media_rule_value_parser()),
1794 ])
1795}
1796
1797fn parenthesized_expression_parser() -> TokenParser<MediaQueryRule> {
1800 TokenParser::new(
1801 |tokens| {
1802 if let Ok(Some(SimpleToken::LeftParen)) = tokens.peek() {
1804 tokens.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1808 tokens.consume_next_token()?;
1809 }
1810
1811 if let Ok(Some(SimpleToken::Ident(keyword))) = tokens.peek()
1813 && keyword == "not"
1814 {
1815 let not_rule = (leading_not_parser().run)(tokens)?;
1817
1818 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1820 tokens.consume_next_token()?;
1821 }
1822
1823 if let Ok(Some(SimpleToken::RightParen)) = tokens.peek() {
1825 tokens.consume_next_token()?; return Ok(not_rule);
1827 } else {
1828 return Err(CssParseError::ParseError {
1829 message: "Expected closing parenthesis after parenthesized NOT expression"
1830 .to_string(),
1831 });
1832 }
1833 }
1834
1835 let inner_expression = (and_combinator_parser().run)(tokens)?;
1837
1838 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
1840 tokens.consume_next_token()?;
1841 }
1842
1843 if let Ok(Some(SimpleToken::RightParen)) = tokens.peek() {
1845 tokens.consume_next_token()?; Ok(inner_expression)
1847 } else {
1848 Err(CssParseError::ParseError {
1849 message: "Expected closing parenthesis after parenthesized expression".to_string(),
1850 })
1851 }
1852 } else {
1853 Err(CssParseError::ParseError {
1854 message: "Expected opening parenthesis for parenthesized expression".to_string(),
1855 })
1856 }
1857 },
1858 "parenthesized_expression_parser",
1859 )
1860}
1861
1862#[cfg(test)]
1863mod tests {
1864 use stylex_macros::stylex_panic;
1865
1866 use super::*;
1867
1868 #[test]
1869 fn test_media_query_creation() {
1870 let query = MediaQuery::parser().parse_to_end("@media screen").unwrap();
1871 assert_eq!(query.to_string(), "@media screen");
1872 }
1873
1874 #[test]
1875 fn test_media_query_display() {
1876 let query = MediaQuery::parser()
1877 .parse_to_end("@media (min-width: 768px)")
1878 .unwrap();
1879 assert_eq!(format!("{}", query), "@media (min-width: 768px)");
1880 }
1881
1882 #[test]
1883 fn test_has_balanced_parens() {
1884 assert!(has_balanced_parens("(min-width: 768px)"));
1885 assert!(has_balanced_parens(
1886 "(min-width: 768px) and (max-width: 1200px)"
1887 ));
1888 assert!(has_balanced_parens("screen"));
1889 assert!(has_balanced_parens(""));
1890
1891 assert!(!has_balanced_parens("(min-width: 768px"));
1892 assert!(!has_balanced_parens("min-width: 768px)"));
1893 assert!(!has_balanced_parens("((min-width: 768px)"));
1894 }
1895
1896 #[test]
1897 fn test_validate_media_query_success() {
1898 let result = validate_media_query("@media (min-width: 768px)");
1899 assert!(result.is_ok());
1900
1901 let query = result.unwrap();
1902 assert_eq!(query.to_string(), "@media (min-width: 768px)");
1903 }
1904
1905 #[test]
1906 fn test_validate_media_query_unbalanced_parens() {
1907 let result = validate_media_query("@media (min-width: 768px");
1908 assert!(result.is_err());
1909 assert!(result.unwrap_err().contains("parentheses"));
1910 }
1911
1912 #[test]
1913 fn test_media_query_parser_creation() {
1914 let _parser = MediaQuery::parser();
1916 }
1917
1918 #[test]
1919 fn test_media_query_equality() {
1920 let query1 = MediaQuery::parser().parse_to_end("@media screen").unwrap();
1921 let query2 = MediaQuery::parser().parse_to_end("@media screen").unwrap();
1922 let query3 = MediaQuery::parser().parse_to_end("@media print").unwrap();
1923
1924 assert_eq!(query1, query2);
1925 assert_ne!(query1, query3);
1926 }
1927
1928 #[test]
1929 fn test_media_query_clone() {
1930 let query = MediaQuery::parser()
1931 .parse_to_end("@media (orientation: landscape)")
1932 .unwrap();
1933 let cloned = query.clone();
1934
1935 assert_eq!(query, cloned);
1936 }
1937
1938 #[test]
1939 fn test_common_media_queries() {
1940 let implemented_queries = vec![
1942 "@media screen",
1943 "@media print",
1944 "@media (min-width: 768px)",
1945 "@media screen and (min-width: 768px)", "@media (min-width: 768px) and (max-width: 1024px)", "@media not screen",
1948 "@media only screen and (min-width: 768px)", ];
1950
1951 for query_str in implemented_queries {
1952 let result = validate_media_query(query_str);
1953 assert!(result.is_ok(), "Failed to validate: {}", query_str);
1954
1955 let query = result.unwrap();
1956 assert_eq!(
1957 query.to_string(),
1958 query_str.replace(" screen and", " (screen) and")
1959 );
1960 println!("✅ Validated: {}", query_str);
1961 }
1962
1963 let edge_case_queries = vec![
1965 ];
1968
1969 for query_str in edge_case_queries {
1970 let result = validate_media_query(query_str);
1971 if result.is_err() {
1972 println!("✅ Correctly rejecting edge case: {}", query_str);
1973 } else {
1974 println!("⚠️ Unexpectedly accepting edge case: {}", query_str);
1975 }
1976 }
1977 }
1978
1979 #[test]
1980 fn test_complex_parentheses() {
1981 let supported_query = "@media (min-width: 768px)";
1982 let result = validate_media_query(supported_query);
1983 assert!(
1984 result.is_ok(),
1985 "Simple parentheses should work: {:?}",
1986 result
1987 );
1988
1989 let and_combinator_query = "@media screen and ((min-width: 768px) and (max-width: 1024px))";
1991 let result = validate_media_query(and_combinator_query);
1992 assert!(
1993 result.is_ok(),
1994 "Complex AND combinators should now work: {:?}",
1995 result
1996 );
1997 println!(
1998 "✅ Complex parentheses with AND combinators now working: {}",
1999 and_combinator_query
2000 );
2001 }
2002
2003 #[test]
2004 fn test_media_query_normalization() {
2005 let input = "@media not (not (not (min-width: 400px)))";
2006 let parsed = MediaQuery::parser().parse_to_end(input).unwrap();
2007 println!("Triple NOT input: {}", input);
2008 println!("Triple NOT output: {}", parsed);
2009
2010 match &parsed.queries {
2012 MediaQueryRule::Not(not_rule) => match ¬_rule.rule.as_ref() {
2013 MediaQueryRule::Pair(pair) => {
2014 assert_eq!(pair.key, "min-width");
2015 println!("✅ Triple NOT correctly normalized to single NOT");
2016 },
2017 _ => stylex_panic!("Expected Pair rule inside NOT, got: {:?}", not_rule.rule),
2018 },
2019 _ => stylex_panic!("Expected NOT rule at top level, got: {:?}", parsed.queries),
2020 }
2021
2022 let input_quad = "@media not (not (not (not (max-width: 500px))))";
2024 let parsed_quad = MediaQuery::parser().parse_to_end(input_quad).unwrap();
2025 println!("Quadruple NOT input: {}", input_quad);
2026 println!("Quadruple NOT output: {}", parsed_quad);
2027
2028 match &parsed_quad.queries {
2030 MediaQueryRule::Pair(pair) => {
2031 assert_eq!(pair.key, "max-width");
2032 println!("✅ Quadruple NOT correctly canceled out");
2033 },
2034 _ => stylex_panic!(
2035 "Expected Pair rule (no NOT), got: {:?}",
2036 parsed_quad.queries
2037 ),
2038 }
2039
2040 let complex_input = "@media (max-width: 1440px) and (not (max-width: 1024px)) and (not (max-width: 768px)) and (not (max-width: 458px))";
2041 let parsed_complex = MediaQuery::parser().parse_to_end(complex_input).unwrap();
2042 println!("Complex input: {}", complex_input);
2043 println!("Complex output: {}", parsed_complex);
2044
2045 match &parsed_complex.queries {
2046 MediaQueryRule::And(and_rules) => {
2047 println!(
2048 "✅ Complex NOT-AND expression normalized to AND with {} rules",
2049 and_rules.rules.len()
2050 );
2051 let has_min = and_rules
2053 .rules
2054 .iter()
2055 .any(|r| matches!(r, MediaQueryRule::Pair(pair) if pair.key.starts_with("min-")));
2056 let has_max = and_rules
2057 .rules
2058 .iter()
2059 .any(|r| matches!(r, MediaQueryRule::Pair(pair) if pair.key.starts_with("max-")));
2060 assert!(
2061 has_min && has_max,
2062 "Should contain both min and max constraints"
2063 );
2064 },
2065 _ => {
2066 println!(
2068 "ℹ️ Complex expression normalized to single rule: {:?}",
2069 parsed_complex.queries
2070 );
2071 },
2072 }
2073 }
2074
2075 #[test]
2076 fn test_nested_unbalanced_parentheses() {
2077 let invalid_queries = vec![
2078 "@media ((min-width: 768px)",
2079 "@media (min-width: 768px))",
2080 "@media (((min-width: 768px)",
2081 "@media (min-width: 768px)))",
2082 ];
2083
2084 for query_str in invalid_queries {
2085 let result = validate_media_query(query_str);
2086 assert!(result.is_err(), "Should have failed: {}", query_str);
2087 }
2088 }
2089}