style/queries/
feature_expression.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Parsing for query feature expressions, like `(foo: bar)` or
6//! `(width >= 400px)`.
7
8use super::feature::{Evaluator, QueryFeatureDescription};
9use super::feature::{FeatureFlags, KeywordDiscriminant};
10use crate::parser::{Parse, ParserContext};
11use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
12use crate::values::computed::{self, Ratio, ToComputedValue};
13use crate::values::specified::{Integer, Length, Number, Resolution};
14use crate::values::CSSFloat;
15use crate::{Atom, Zero};
16use cssparser::{Parser, Token};
17use selectors::kleene_value::KleeneValue;
18use std::cmp::Ordering;
19use std::fmt::{self, Write};
20use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
21
22/// Whether we're parsing a media or container query feature.
23#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
24pub enum FeatureType {
25    /// We're parsing a media feature.
26    Media,
27    /// We're parsing a container feature.
28    Container,
29}
30
31impl FeatureType {
32    fn features(&self) -> &'static [QueryFeatureDescription] {
33        #[cfg(feature = "gecko")]
34        use crate::gecko::media_features::MEDIA_FEATURES;
35        #[cfg(feature = "servo")]
36        use crate::servo::media_queries::MEDIA_FEATURES;
37
38        use crate::stylesheets::container_rule::CONTAINER_FEATURES;
39
40        match *self {
41            FeatureType::Media => &MEDIA_FEATURES,
42            FeatureType::Container => &CONTAINER_FEATURES,
43        }
44    }
45
46    fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
47        self.features()
48            .iter()
49            .enumerate()
50            .find(|(_, f)| f.name == *name)
51    }
52}
53
54/// The kind of matching that should be performed on a feature value.
55#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
56enum LegacyRange {
57    /// At least the specified value.
58    Min,
59    /// At most the specified value.
60    Max,
61}
62
63/// The operator that was specified in this feature.
64#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
65enum Operator {
66    /// =
67    Equal,
68    /// >
69    GreaterThan,
70    /// >=
71    GreaterThanEqual,
72    /// <
73    LessThan,
74    /// <=
75    LessThanEqual,
76}
77
78impl ToCss for Operator {
79    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
80    where
81        W: fmt::Write,
82    {
83        dest.write_str(match *self {
84            Self::Equal => "=",
85            Self::LessThan => "<",
86            Self::LessThanEqual => "<=",
87            Self::GreaterThan => ">",
88            Self::GreaterThanEqual => ">=",
89        })
90    }
91}
92
93impl Operator {
94    fn is_compatible_with(self, right_op: Self) -> bool {
95        // Some operators are not compatible with each other in multi-range
96        // context.
97        match self {
98            Self::Equal => false,
99            Self::GreaterThan | Self::GreaterThanEqual => {
100                matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual)
101            },
102            Self::LessThan | Self::LessThanEqual => {
103                matches!(right_op, Self::LessThan | Self::LessThanEqual)
104            },
105        }
106    }
107
108    fn evaluate(&self, cmp: Ordering) -> bool {
109        match *self {
110            Self::Equal => cmp == Ordering::Equal,
111            Self::GreaterThan => cmp == Ordering::Greater,
112            Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
113            Self::LessThan => cmp == Ordering::Less,
114            Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
115        }
116    }
117
118    fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
119        let location = input.current_source_location();
120        let operator = match *input.next()? {
121            Token::Delim('=') => return Ok(Operator::Equal),
122            Token::Delim('>') => Operator::GreaterThan,
123            Token::Delim('<') => Operator::LessThan,
124            ref t => return Err(location.new_unexpected_token_error(t.clone())),
125        };
126
127        // https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
128        //
129        //     No whitespace is allowed between the “<” or “>”
130        //     <delim-token>s and the following “=” <delim-token>, if it’s
131        //     present.
132        //
133        // TODO(emilio): Maybe we should ignore comments as well?
134        // https://github.com/w3c/csswg-drafts/issues/6248
135        let parsed_equal = input
136            .try_parse(|i| {
137                let t = i.next_including_whitespace().map_err(|_| ())?;
138                if !matches!(t, Token::Delim('=')) {
139                    return Err(());
140                }
141                Ok(())
142            })
143            .is_ok();
144
145        if !parsed_equal {
146            return Ok(operator);
147        }
148
149        Ok(match operator {
150            Operator::GreaterThan => Operator::GreaterThanEqual,
151            Operator::LessThan => Operator::LessThanEqual,
152            _ => unreachable!(),
153        })
154    }
155}
156
157#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
158enum QueryFeatureExpressionKind {
159    /// Just the media feature name.
160    Empty,
161
162    /// A single value.
163    Single(QueryExpressionValue),
164
165    /// Legacy range syntax (min-*: value) or so.
166    LegacyRange(LegacyRange, QueryExpressionValue),
167
168    /// Modern range context syntax:
169    /// https://drafts.csswg.org/mediaqueries-5/#mq-range-context
170    Range {
171        left: Option<(Operator, QueryExpressionValue)>,
172        right: Option<(Operator, QueryExpressionValue)>,
173    },
174}
175
176impl QueryFeatureExpressionKind {
177    /// Evaluate a given range given an optional query value and a value from
178    /// the browser.
179    fn evaluate<T>(
180        &self,
181        context_value: T,
182        mut compute: impl FnMut(&QueryExpressionValue) -> T,
183    ) -> bool
184    where
185        T: PartialOrd + Zero,
186    {
187        match *self {
188            Self::Empty => return !context_value.is_zero(),
189            Self::Single(ref value) => {
190                let value = compute(value);
191                let cmp = match context_value.partial_cmp(&value) {
192                    Some(c) => c,
193                    None => return false,
194                };
195                cmp == Ordering::Equal
196            },
197            Self::LegacyRange(ref range, ref value) => {
198                let value = compute(value);
199                let cmp = match context_value.partial_cmp(&value) {
200                    Some(c) => c,
201                    None => return false,
202                };
203                cmp == Ordering::Equal
204                    || match range {
205                        LegacyRange::Min => cmp == Ordering::Greater,
206                        LegacyRange::Max => cmp == Ordering::Less,
207                    }
208            },
209            Self::Range {
210                ref left,
211                ref right,
212            } => {
213                debug_assert!(left.is_some() || right.is_some());
214                if let Some((ref op, ref value)) = left {
215                    let value = compute(value);
216                    let cmp = match value.partial_cmp(&context_value) {
217                        Some(c) => c,
218                        None => return false,
219                    };
220                    if !op.evaluate(cmp) {
221                        return false;
222                    }
223                }
224                if let Some((ref op, ref value)) = right {
225                    let value = compute(value);
226                    let cmp = match context_value.partial_cmp(&value) {
227                        Some(c) => c,
228                        None => return false,
229                    };
230                    if !op.evaluate(cmp) {
231                        return false;
232                    }
233                }
234                true
235            },
236        }
237    }
238
239    /// Non-ranged features only need to compare to one value at most.
240    fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
241        match *self {
242            Self::Empty => None,
243            Self::Single(ref v) => Some(v),
244            Self::LegacyRange(..) | Self::Range { .. } => {
245                debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
246                None
247            },
248        }
249    }
250}
251
252/// A feature expression contains a reference to the feature, the value the
253/// query contained, and the range to evaluate.
254#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
255pub struct QueryFeatureExpression {
256    feature_type: FeatureType,
257    feature_index: usize,
258    kind: QueryFeatureExpressionKind,
259}
260
261impl ToCss for QueryFeatureExpression {
262    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
263    where
264        W: fmt::Write,
265    {
266        dest.write_char('(')?;
267
268        match self.kind {
269            QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
270            QueryFeatureExpressionKind::Single(ref v)
271            | QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
272                self.write_name(dest)?;
273                dest.write_str(": ")?;
274                v.to_css(dest, self)?;
275            },
276            QueryFeatureExpressionKind::Range {
277                ref left,
278                ref right,
279            } => {
280                if let Some((ref op, ref val)) = left {
281                    val.to_css(dest, self)?;
282                    dest.write_char(' ')?;
283                    op.to_css(dest)?;
284                    dest.write_char(' ')?;
285                }
286                self.write_name(dest)?;
287                if let Some((ref op, ref val)) = right {
288                    dest.write_char(' ')?;
289                    op.to_css(dest)?;
290                    dest.write_char(' ')?;
291                    val.to_css(dest, self)?;
292                }
293            },
294        }
295        dest.write_char(')')
296    }
297}
298
299fn consume_operation_or_colon<'i>(
300    input: &mut Parser<'i, '_>,
301) -> Result<Option<Operator>, ParseError<'i>> {
302    if input.try_parse(|input| input.expect_colon()).is_ok() {
303        return Ok(None);
304    }
305    Operator::parse(input).map(|op| Some(op))
306}
307
308#[allow(unused_variables)]
309fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
310    #[cfg(feature = "gecko")]
311    {
312        // prefers-reduced-transparency is always enabled in the ua and chrome. On
313        // the web it is hidden behind a preference (see Bug 1822176).
314        if *feature == atom!("prefers-reduced-transparency") {
315            return !context.chrome_rules_enabled()
316                && !static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
317        }
318
319        // inverted-colors is always enabled in the ua and chrome. On
320        // the web it is hidden behind a preference.
321        if *feature == atom!("inverted-colors") {
322            return !context.chrome_rules_enabled()
323                && !static_prefs::pref!("layout.css.inverted-colors.enabled");
324        }
325    }
326    false
327}
328
329impl QueryFeatureExpression {
330    fn new(
331        feature_type: FeatureType,
332        feature_index: usize,
333        kind: QueryFeatureExpressionKind,
334    ) -> Self {
335        debug_assert!(feature_index < feature_type.features().len());
336        Self {
337            feature_type,
338            feature_index,
339            kind,
340        }
341    }
342
343    fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
344    where
345        W: fmt::Write,
346    {
347        let feature = self.feature();
348        if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
349            dest.write_str("-webkit-")?;
350        }
351
352        if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
353            match range {
354                LegacyRange::Min => dest.write_str("min-")?,
355                LegacyRange::Max => dest.write_str("max-")?,
356            }
357        }
358
359        // NB: CssStringWriter not needed, feature names are under control.
360        write!(dest, "{}", feature.name)?;
361
362        Ok(())
363    }
364
365    fn feature(&self) -> &'static QueryFeatureDescription {
366        &self.feature_type.features()[self.feature_index]
367    }
368
369    /// Returns the feature flags for our feature.
370    pub fn feature_flags(&self) -> FeatureFlags {
371        self.feature().flags
372    }
373
374    /// Parse a feature expression of the form:
375    ///
376    /// ```
377    /// (media-feature: media-value)
378    /// ```
379    pub fn parse<'i, 't>(
380        context: &ParserContext,
381        input: &mut Parser<'i, 't>,
382        feature_type: FeatureType,
383    ) -> Result<Self, ParseError<'i>> {
384        input.expect_parenthesis_block()?;
385        input.parse_nested_block(|input| {
386            Self::parse_in_parenthesis_block(context, input, feature_type)
387        })
388    }
389
390    fn parse_feature_name<'i, 't>(
391        context: &ParserContext,
392        input: &mut Parser<'i, 't>,
393        feature_type: FeatureType,
394    ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
395        let mut flags = FeatureFlags::empty();
396        let location = input.current_source_location();
397        let ident = input.expect_ident()?;
398
399        if context.chrome_rules_enabled() {
400            flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
401        }
402
403        let mut feature_name = &**ident;
404        if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
405            feature_name = &feature_name[8..];
406            flags.insert(FeatureFlags::WEBKIT_PREFIX);
407        }
408
409        let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
410            feature_name = &feature_name[4..];
411            Some(LegacyRange::Min)
412        } else if starts_with_ignore_ascii_case(feature_name, "max-") {
413            feature_name = &feature_name[4..];
414            Some(LegacyRange::Max)
415        } else {
416            None
417        };
418
419        let atom = Atom::from(string_as_ascii_lowercase(feature_name));
420        let (feature_index, feature) = match feature_type.find_feature(&atom) {
421            Some((i, f)) => (i, f),
422            None => {
423                return Err(location.new_custom_error(
424                    StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
425                ))
426            },
427        };
428
429        if disabled_by_pref(&feature.name, context)
430            || !flags.contains(feature.flags.parsing_requirements())
431            || (range.is_some() && !feature.allows_ranges())
432        {
433            return Err(location.new_custom_error(
434                StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
435            ));
436        }
437
438        Ok((feature_index, range))
439    }
440
441    /// Parses the following range syntax:
442    ///
443    ///   (feature-value <operator> feature-name)
444    ///   (feature-value <operator> feature-name <operator> feature-value)
445    fn parse_multi_range_syntax<'i, 't>(
446        context: &ParserContext,
447        input: &mut Parser<'i, 't>,
448        feature_type: FeatureType,
449    ) -> Result<Self, ParseError<'i>> {
450        let start = input.state();
451
452        // To parse the values, we first need to find the feature name. We rely
453        // on feature values for ranged features not being able to be top-level
454        // <ident>s, which holds.
455        let feature_index = loop {
456            // NOTE: parse_feature_name advances the input.
457            if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
458                if range.is_some() {
459                    // Ranged names are not allowed here.
460                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
461                }
462                break index;
463            }
464            if input.is_exhausted() {
465                return Err(start
466                    .source_location()
467                    .new_custom_error(StyleParseErrorKind::UnspecifiedError));
468            }
469        };
470
471        input.reset(&start);
472
473        let feature = &feature_type.features()[feature_index];
474        let left_val = QueryExpressionValue::parse(feature, context, input)?;
475        let left_op = Operator::parse(input)?;
476
477        {
478            let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
479            debug_assert_eq!(
480                parsed_index, feature_index,
481                "How did we find a different feature?"
482            );
483        }
484
485        let right_op = input.try_parse(Operator::parse).ok();
486        let right = match right_op {
487            Some(op) => {
488                if !left_op.is_compatible_with(op) {
489                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
490                }
491                Some((op, QueryExpressionValue::parse(feature, context, input)?))
492            },
493            None => None,
494        };
495        Ok(Self::new(
496            feature_type,
497            feature_index,
498            QueryFeatureExpressionKind::Range {
499                left: Some((left_op, left_val)),
500                right,
501            },
502        ))
503    }
504
505    /// Parse a feature expression where we've already consumed the parenthesis.
506    pub fn parse_in_parenthesis_block<'i, 't>(
507        context: &ParserContext,
508        input: &mut Parser<'i, 't>,
509        feature_type: FeatureType,
510    ) -> Result<Self, ParseError<'i>> {
511        let (feature_index, range) =
512            match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
513                Ok(v) => v,
514                Err(e) => {
515                    if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
516                        return Ok(expr);
517                    }
518                    return Err(e);
519                },
520            };
521        let operator = input.try_parse(consume_operation_or_colon);
522        let operator = match operator {
523            Err(..) => {
524                // If there's no colon, this is a query of the form
525                // '(<feature>)', that is, there's no value specified.
526                //
527                // Gecko doesn't allow ranged expressions without a
528                // value, so just reject them here too.
529                if range.is_some() {
530                    return Err(
531                        input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
532                    );
533                }
534
535                return Ok(Self::new(
536                    feature_type,
537                    feature_index,
538                    QueryFeatureExpressionKind::Empty,
539                ));
540            },
541            Ok(operator) => operator,
542        };
543
544        let feature = &feature_type.features()[feature_index];
545
546        let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
547            err.location
548                .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
549        })?;
550
551        let kind = match range {
552            Some(range) => {
553                if operator.is_some() {
554                    return Err(
555                        input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
556                    );
557                }
558                QueryFeatureExpressionKind::LegacyRange(range, value)
559            },
560            None => match operator {
561                Some(operator) => {
562                    if !feature.allows_ranges() {
563                        return Err(input
564                            .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
565                    }
566                    QueryFeatureExpressionKind::Range {
567                        left: None,
568                        right: Some((operator, value)),
569                    }
570                },
571                None => QueryFeatureExpressionKind::Single(value),
572            },
573        };
574
575        Ok(Self::new(feature_type, feature_index, kind))
576    }
577
578    /// Returns whether this query evaluates to true for the given device.
579    pub fn matches(&self, context: &computed::Context) -> KleeneValue {
580        macro_rules! expect {
581            ($variant:ident, $v:expr) => {
582                match *$v {
583                    QueryExpressionValue::$variant(ref v) => v,
584                    _ => unreachable!("Unexpected QueryExpressionValue"),
585                }
586            };
587        }
588
589        KleeneValue::from(match self.feature().evaluator {
590            Evaluator::Length(eval) => {
591                let v = eval(context);
592                self.kind
593                    .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
594            },
595            Evaluator::OptionalLength(eval) => {
596                let v = match eval(context) {
597                    Some(v) => v,
598                    None => return KleeneValue::Unknown,
599                };
600                self.kind
601                    .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
602            },
603            Evaluator::Integer(eval) => {
604                let v = eval(context);
605                self.kind.evaluate(v, |v| *expect!(Integer, v))
606            },
607            Evaluator::Float(eval) => {
608                let v = eval(context);
609                self.kind.evaluate(v, |v| *expect!(Float, v))
610            },
611            Evaluator::NumberRatio(eval) => {
612                let ratio = eval(context);
613                // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
614                // to convert it if necessary.
615                // FIXME: we may need to update here once
616                // https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
617                self.kind
618                    .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
619            },
620            Evaluator::OptionalNumberRatio(eval) => {
621                let ratio = match eval(context) {
622                    Some(v) => v,
623                    None => return KleeneValue::Unknown,
624                };
625                // See above for subtleties here.
626                self.kind
627                    .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
628            },
629            Evaluator::Resolution(eval) => {
630                let v = eval(context).dppx();
631                self.kind.evaluate(v, |v| {
632                    expect!(Resolution, v).to_computed_value(context).dppx()
633                })
634            },
635            Evaluator::Enumerated { evaluator, .. } => {
636                let computed = self
637                    .kind
638                    .non_ranged_value()
639                    .map(|v| *expect!(Enumerated, v));
640                return evaluator(context, computed);
641            },
642            Evaluator::BoolInteger(eval) => {
643                let computed = self
644                    .kind
645                    .non_ranged_value()
646                    .map(|v| *expect!(BoolInteger, v));
647                let boolean = eval(context);
648                computed.map_or(boolean, |v| v == boolean)
649            },
650        })
651    }
652}
653
654/// A value found or expected in a expression.
655///
656/// FIXME(emilio): How should calc() serialize in the Number / Integer /
657/// BoolInteger / NumberRatio case, as computed or as specified value?
658///
659/// If the first, this would need to store the relevant values.
660///
661/// See: https://github.com/w3c/csswg-drafts/issues/1968
662#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
663pub enum QueryExpressionValue {
664    /// A length.
665    Length(Length),
666    /// An integer.
667    Integer(i32),
668    /// A floating point value.
669    Float(CSSFloat),
670    /// A boolean value, specified as an integer (i.e., either 0 or 1).
671    BoolInteger(bool),
672    /// A single non-negative number or two non-negative numbers separated by '/',
673    /// with optional whitespace on either side of the '/'.
674    NumberRatio(Ratio),
675    /// A resolution.
676    Resolution(Resolution),
677    /// An enumerated value, defined by the variant keyword table in the
678    /// feature's `mData` member.
679    Enumerated(KeywordDiscriminant),
680}
681
682impl QueryExpressionValue {
683    fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
684    where
685        W: fmt::Write,
686    {
687        match *self {
688            QueryExpressionValue::Length(ref l) => l.to_css(dest),
689            QueryExpressionValue::Integer(v) => v.to_css(dest),
690            QueryExpressionValue::Float(v) => v.to_css(dest),
691            QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
692            QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
693            QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
694            QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
695                Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
696                _ => unreachable!(),
697            },
698        }
699    }
700
701    fn parse<'i, 't>(
702        for_feature: &QueryFeatureDescription,
703        context: &ParserContext,
704        input: &mut Parser<'i, 't>,
705    ) -> Result<QueryExpressionValue, ParseError<'i>> {
706        Ok(match for_feature.evaluator {
707            Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
708                let length = Length::parse(context, input)?;
709                QueryExpressionValue::Length(length)
710            },
711            Evaluator::Integer(..) => {
712                let integer = Integer::parse(context, input)?;
713                QueryExpressionValue::Integer(integer.value())
714            },
715            Evaluator::BoolInteger(..) => {
716                let integer = Integer::parse_non_negative(context, input)?;
717                let value = integer.value();
718                if value > 1 {
719                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
720                }
721                QueryExpressionValue::BoolInteger(value == 1)
722            },
723            Evaluator::Float(..) => {
724                let number = Number::parse(context, input)?;
725                QueryExpressionValue::Float(number.get())
726            },
727            Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
728                use crate::values::specified::Ratio as SpecifiedRatio;
729                let ratio = SpecifiedRatio::parse(context, input)?;
730                QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
731            },
732            Evaluator::Resolution(..) => {
733                QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
734            },
735            Evaluator::Enumerated { parser, .. } => {
736                QueryExpressionValue::Enumerated(parser(context, input)?)
737            },
738        })
739    }
740}