<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Gecko's media-query device and expression representation.

use app_units::Au;
use cssparser::{Parser, Token};
use euclid::Size2D;
use gecko_bindings::bindings;
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
use gecko_bindings::structs::RawGeckoPresContextOwned;
use media_queries::MediaType;
use properties::ComputedValues;
use std::ascii::AsciiExt;
use std::fmt;
use std::sync::Arc;
use string_cache::Atom;
use style_traits::ToCss;
use style_traits::viewport::ViewportConstraints;
use values::{CSSFloat, specified};

/// The `Device` in Gecko wraps a pres context, has a default values computed,
/// and contains all the viewport rule state.
pub struct Device {
    /// NB: The pres context lifetime is tied to the styleset, who owns the
    /// stylist, and thus the `Device`, so having a raw pres context pointer
    /// here is fine.
    pres_context: RawGeckoPresContextOwned,
    default_values: Arc<ComputedValues>,
    viewport_override: Option<ViewportConstraints>,
}

impl Device {
    /// Trivially constructs a new `Device`.
    pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
        assert!(!pres_context.is_null());
        Device {
            pres_context: pres_context,
            default_values: ComputedValues::default_values(unsafe { &*pres_context }),
            viewport_override: None,
        }
    }

    /// Tells the device that a new viewport rule has been found, and stores the
    /// relevant viewport constraints.
    pub fn account_for_viewport_rule(&mut self,
                                     constraints: &ViewportConstraints) {
        self.viewport_override = Some(constraints.clone());
    }

    /// Returns the default computed values as a reference, in order to match
    /// Servo.
    pub fn default_values(&self) -> &ComputedValues {
        &*self.default_values
    }

    /// Returns the default computed values as an `Arc`, in order to avoid
    /// clones.
    pub fn default_values_arc(&self) -> &Arc<ComputedValues> {
        &self.default_values
    }

    /// Recreates all the temporary state that the `Device` stores.
    ///
    /// This includes the viewport override from `@viewport` rules, and also the
    /// default computed values.
    pub fn reset(&mut self) {
        // NB: A following stylesheet flush will populate this if appropriate.
        self.viewport_override = None;
        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
    }

    /// Returns the current media type of the device.
    pub fn media_type(&self) -> MediaType {
        // TODO
        MediaType::Screen
    }

    /// Returns the current viewport size in app units.
    pub fn au_viewport_size(&self) -> Size2D<Au> {
        self.viewport_override.as_ref().map(|v| {
            Size2D::new(Au::from_f32_px(v.size.width),
                        Au::from_f32_px(v.size.height))
        }).unwrap_or_else(|| {
            // TODO(emilio): Grab from pres context.
            Size2D::new(Au::from_f32_px(1024.0),
                        Au::from_f32_px(768.0))
        })
    }
}

unsafe impl Sync for Device {}
unsafe impl Send for Device {}

/// A expression for gecko contains a reference to the media feature, the value
/// the media query contained, and the range to evaluate.
#[derive(Debug, Clone)]
pub struct Expression {
    feature: &'static nsMediaFeature,
    value: MediaExpressionValue,
    range: nsMediaExpression_Range
}

impl ToCss for Expression {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        dest.write_str("(")?;
        match self.range {
            nsMediaExpression_Range::eMin => dest.write_str("min-")?,
            nsMediaExpression_Range::eMax => dest.write_str("max-")?,
            nsMediaExpression_Range::eEqual => {},
        }
        // NB: CSSStringWriter not needed, features are under control.
        write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
        dest.write_str(": ")?;

        self.value.to_css(dest)?;
        dest.write_str(")")
    }
}

/// A resolution.
#[derive(Debug, Clone)]
pub enum Resolution {
    /// Dots per inch.
    Dpi(CSSFloat),
    /// Dots per pixel.
    Dppx(CSSFloat),
    /// Dots per centimeter.
    Dpcm(CSSFloat),
}

impl Resolution {
    fn parse(input: &mut Parser) -> Result<Self, ()> {
        let (value, unit) = match try!(input.next()) {
            Token::Dimension(value, unit) => {
                (value.value, unit)
            },
            _ => return Err(()),
        };

        Ok(match_ignore_ascii_case! { unit,
            "dpi" => Resolution::Dpi(value),
            "dppx" => Resolution::Dppx(value),
            "dpcm" => Resolution::Dpcm(value),
            _ => return Err(())
        })
    }
}

impl ToCss for Resolution {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        match *self {
            Resolution::Dpi(v) => write!(dest, "{}dpi", v),
            Resolution::Dppx(v) => write!(dest, "{}dppx", v),
            Resolution::Dpcm(v) => write!(dest, "{}dpcm", v),
        }
    }
}

/// A value found or expected in a media expression.
#[derive(Debug, Clone)]
pub enum MediaExpressionValue {
    /// A length.
    Length(specified::Length),
    /// A (non-negative) integer.
    Integer(u32),
    /// A floating point value.
    Float(CSSFloat),
    /// A boolean value, specified as an integer (i.e., either 0 or 1).
    BoolInteger(bool),
    /// Two integers separated by '/', with optional whitespace on either side
    /// of the '/'.
    IntRatio(u32, u32),
    /// A resolution.
    Resolution(Resolution),
    /// An enumerated index into the variant keyword table.
    Enumerated(u32),
    /// An identifier.
    Ident(Atom),
}

impl ToCss for MediaExpressionValue {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        match *self {
            MediaExpressionValue::Length(ref l) => l.to_css(dest),
            MediaExpressionValue::Integer(v) => write!(dest, "{}", v),
            MediaExpressionValue::Float(v) => write!(dest, "{}", v),
            MediaExpressionValue::BoolInteger(v) => {
                dest.write_str(if v { "1" } else { "0" })
            },
            MediaExpressionValue::IntRatio(a, b) => {
                write!(dest, "{}/{}", a, b)
            },
            MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
            MediaExpressionValue::Enumerated(..) |
            MediaExpressionValue::Ident(..) => {
                // TODO(emilio)
                unimplemented!()
            }
        }
    }
}

fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
    string.len() > prefix.len() &&
      string[0..prefix.len()].eq_ignore_ascii_case(prefix)
}

#[allow(warnings)]
fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
    where F: FnMut(&'static nsMediaFeature) -> bool,
{
    // FIXME(emilio): With build-time bindgen, we would be able to use
    // structs::nsMediaFeatures_features. That would unfortunately break MSVC
    // builds, or require one bindings file per platform.
    //
    // I'm not into any of those, so meanwhile let's use a FFI function.
    unsafe {
        let mut features = bindings::Gecko_GetMediaFeatures();
        while !(*features).mName.is_null() {
            if f(&*features) {
                return Some(&*features);
            }
            features = features.offset(1);
        }
    }

    None
}

impl Expression {
    /// Trivially construct a new expression.
    fn new(feature: &'static nsMediaFeature,
           value: MediaExpressionValue,
           range: nsMediaExpression_Range) -> Self {
        Expression {
            feature: feature,
            value: value,
            range: range,
        }
    }

    /// Parse a media expression of the form:
    ///
    /// ```
    /// (media-feature: media-value)
    /// ```
    #[allow(warnings)]
    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
        try!(input.expect_parenthesis_block());
        input.parse_nested_block(|input| {
            let ident = try!(input.expect_ident());
            try!(input.expect_colon());

            let mut flags = 0;
            let mut feature_name = &*ident;

            // TODO(emilio): this is under a pref in Gecko.
            if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
                feature_name = &feature_name[8..];
                flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
            }

            let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
                feature_name = &feature_name[4..];
                nsMediaExpression_Range::eMin
            } else if starts_with_ignore_ascii_case(feature_name, "max-") {
                feature_name = &feature_name[4..];
                nsMediaExpression_Range::eMax
            } else {
                nsMediaExpression_Range::eEqual
            };

            let atom = Atom::from(feature_name);
            let feature =
                match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
                    Some(f) => f,
                    None => return Err(()),
                };

            if (feature.mReqFlags & !flags) != 0 {
                return Err(());
            }

            if range != nsMediaExpression_Range::eEqual &&
                feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
                return Err(());
            }

            let value = match feature.mValueType {
                nsMediaFeature_ValueType::eLength => {
                    MediaExpressionValue::Length(
                        specified::Length::parse_non_negative(input)?)
                },
                nsMediaFeature_ValueType::eInteger => {
                    let i = input.expect_integer()?;
                    if i < 0 {
                        return Err(())
                    }
                    MediaExpressionValue::Integer(i as u32)
                }
                nsMediaFeature_ValueType::eBoolInteger => {
                    let i = input.expect_integer()?;
                    if i < 0 || i > 1 {
                        return Err(())
                    }
                    MediaExpressionValue::BoolInteger(i == 1)
                }
                nsMediaFeature_ValueType::eFloat => {
                    MediaExpressionValue::Float(input.expect_number()?)
                }
                nsMediaFeature_ValueType::eIntRatio => {
                    let a = input.expect_integer()?;
                    if a <= 0 {
                        return Err(())
                    }

                    input.expect_delim('/')?;

                    let b = input.expect_integer()?;
                    if b <= 0 {
                        return Err(())
                    }
                    MediaExpressionValue::IntRatio(a as u32, b as u32)
                }
                nsMediaFeature_ValueType::eResolution => {
                    MediaExpressionValue::Resolution(Resolution::parse(input)?)
                }
                nsMediaFeature_ValueType::eEnumerated => {
                    let index = unsafe {
                        let _table = feature.mData.mKeywordTable.as_ref();
                        0
                    };
                    MediaExpressionValue::Enumerated(index)
                }
                nsMediaFeature_ValueType::eIdent => {
                    MediaExpressionValue::Ident(input.expect_ident()?.into())
                }
            };

            Ok(Expression::new(feature, value, range))
        })
    }

    /// Returns whether this media query evaluates to true for the given
    /// device.
    pub fn matches(&self, _device: &Device) -> bool {
        // TODO
        false
    }
}
<commit_msg>Bug 1331213: Allow parsing media query expressions without colons. r=heycam<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Gecko's media-query device and expression representation.

use app_units::Au;
use cssparser::{Parser, Token};
use euclid::Size2D;
use gecko_bindings::bindings;
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
use gecko_bindings::structs::RawGeckoPresContextOwned;
use media_queries::MediaType;
use properties::ComputedValues;
use std::ascii::AsciiExt;
use std::fmt;
use std::sync::Arc;
use string_cache::Atom;
use style_traits::ToCss;
use style_traits::viewport::ViewportConstraints;
use values::{CSSFloat, specified};

/// The `Device` in Gecko wraps a pres context, has a default values computed,
/// and contains all the viewport rule state.
pub struct Device {
    /// NB: The pres context lifetime is tied to the styleset, who owns the
    /// stylist, and thus the `Device`, so having a raw pres context pointer
    /// here is fine.
    pres_context: RawGeckoPresContextOwned,
    default_values: Arc<ComputedValues>,
    viewport_override: Option<ViewportConstraints>,
}

impl Device {
    /// Trivially constructs a new `Device`.
    pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
        assert!(!pres_context.is_null());
        Device {
            pres_context: pres_context,
            default_values: ComputedValues::default_values(unsafe { &*pres_context }),
            viewport_override: None,
        }
    }

    /// Tells the device that a new viewport rule has been found, and stores the
    /// relevant viewport constraints.
    pub fn account_for_viewport_rule(&mut self,
                                     constraints: &ViewportConstraints) {
        self.viewport_override = Some(constraints.clone());
    }

    /// Returns the default computed values as a reference, in order to match
    /// Servo.
    pub fn default_values(&self) -> &ComputedValues {
        &*self.default_values
    }

    /// Returns the default computed values as an `Arc`, in order to avoid
    /// clones.
    pub fn default_values_arc(&self) -> &Arc<ComputedValues> {
        &self.default_values
    }

    /// Recreates all the temporary state that the `Device` stores.
    ///
    /// This includes the viewport override from `@viewport` rules, and also the
    /// default computed values.
    pub fn reset(&mut self) {
        // NB: A following stylesheet flush will populate this if appropriate.
        self.viewport_override = None;
        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
    }

    /// Returns the current media type of the device.
    pub fn media_type(&self) -> MediaType {
        // TODO
        MediaType::Screen
    }

    /// Returns the current viewport size in app units.
    pub fn au_viewport_size(&self) -> Size2D<Au> {
        self.viewport_override.as_ref().map(|v| {
            Size2D::new(Au::from_f32_px(v.size.width),
                        Au::from_f32_px(v.size.height))
        }).unwrap_or_else(|| {
            // TODO(emilio): Grab from pres context.
            Size2D::new(Au::from_f32_px(1024.0),
                        Au::from_f32_px(768.0))
        })
    }
}

unsafe impl Sync for Device {}
unsafe impl Send for Device {}

/// A expression for gecko contains a reference to the media feature, the value
/// the media query contained, and the range to evaluate.
#[derive(Debug, Clone)]
pub struct Expression {
    feature: &'static nsMediaFeature,
    value: Option<MediaExpressionValue>,
    range: nsMediaExpression_Range
}

impl ToCss for Expression {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        dest.write_str("(")?;
        match self.range {
            nsMediaExpression_Range::eMin => dest.write_str("min-")?,
            nsMediaExpression_Range::eMax => dest.write_str("max-")?,
            nsMediaExpression_Range::eEqual => {},
        }

        // NB: CSSStringWriter not needed, feature names are under control.
        write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;

        if let Some(ref val) = self.value {
            dest.write_str(": ")?;
            val.to_css(dest)?;
        }

        dest.write_str(")")
    }
}

/// A resolution.
#[derive(Debug, Clone)]
pub enum Resolution {
    /// Dots per inch.
    Dpi(CSSFloat),
    /// Dots per pixel.
    Dppx(CSSFloat),
    /// Dots per centimeter.
    Dpcm(CSSFloat),
}

impl Resolution {
    fn parse(input: &mut Parser) -> Result<Self, ()> {
        let (value, unit) = match try!(input.next()) {
            Token::Dimension(value, unit) => {
                (value.value, unit)
            },
            _ => return Err(()),
        };

        Ok(match_ignore_ascii_case! { unit,
            "dpi" => Resolution::Dpi(value),
            "dppx" => Resolution::Dppx(value),
            "dpcm" => Resolution::Dpcm(value),
            _ => return Err(())
        })
    }
}

impl ToCss for Resolution {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        match *self {
            Resolution::Dpi(v) => write!(dest, "{}dpi", v),
            Resolution::Dppx(v) => write!(dest, "{}dppx", v),
            Resolution::Dpcm(v) => write!(dest, "{}dpcm", v),
        }
    }
}

/// A value found or expected in a media expression.
#[derive(Debug, Clone)]
pub enum MediaExpressionValue {
    /// A length.
    Length(specified::Length),
    /// A (non-negative) integer.
    Integer(u32),
    /// A floating point value.
    Float(CSSFloat),
    /// A boolean value, specified as an integer (i.e., either 0 or 1).
    BoolInteger(bool),
    /// Two integers separated by '/', with optional whitespace on either side
    /// of the '/'.
    IntRatio(u32, u32),
    /// A resolution.
    Resolution(Resolution),
    /// An enumerated index into the variant keyword table.
    Enumerated(u32),
    /// An identifier.
    Ident(Atom),
}

impl ToCss for MediaExpressionValue {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
        where W: fmt::Write,
    {
        match *self {
            MediaExpressionValue::Length(ref l) => l.to_css(dest),
            MediaExpressionValue::Integer(v) => write!(dest, "{}", v),
            MediaExpressionValue::Float(v) => write!(dest, "{}", v),
            MediaExpressionValue::BoolInteger(v) => {
                dest.write_str(if v { "1" } else { "0" })
            },
            MediaExpressionValue::IntRatio(a, b) => {
                write!(dest, "{}/{}", a, b)
            },
            MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
            MediaExpressionValue::Enumerated(..) |
            MediaExpressionValue::Ident(..) => {
                // TODO(emilio)
                unimplemented!()
            }
        }
    }
}

fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
    string.len() > prefix.len() &&
      string[0..prefix.len()].eq_ignore_ascii_case(prefix)
}

fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
    where F: FnMut(&'static nsMediaFeature) -> bool,
{
    // FIXME(emilio): With build-time bindgen, we would be able to use
    // structs::nsMediaFeatures_features. That would unfortunately break MSVC
    // builds, or require one bindings file per platform.
    //
    // I'm not into any of those, so meanwhile let's use a FFI function.
    unsafe {
        let mut features = bindings::Gecko_GetMediaFeatures();
        while !(*features).mName.is_null() {
            if f(&*features) {
                return Some(&*features);
            }
            features = features.offset(1);
        }
    }

    None
}

impl Expression {
    /// Trivially construct a new expression.
    fn new(feature: &'static nsMediaFeature,
           value: Option<MediaExpressionValue>,
           range: nsMediaExpression_Range) -> Self {
        Expression {
            feature: feature,
            value: value,
            range: range,
        }
    }

    /// Parse a media expression of the form:
    ///
    /// ```
    /// (media-feature: media-value)
    /// ```
    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
        try!(input.expect_parenthesis_block());
        input.parse_nested_block(|input| {
            let ident = try!(input.expect_ident());

            let mut flags = 0;
            let mut feature_name = &*ident;

            // TODO(emilio): this is under a pref in Gecko.
            if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
                feature_name = &feature_name[8..];
                flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
            }

            let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
                feature_name = &feature_name[4..];
                nsMediaExpression_Range::eMin
            } else if starts_with_ignore_ascii_case(feature_name, "max-") {
                feature_name = &feature_name[4..];
                nsMediaExpression_Range::eMax
            } else {
                nsMediaExpression_Range::eEqual
            };

            let atom = Atom::from(feature_name);
            let feature =
                match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
                    Some(f) => f,
                    None => return Err(()),
                };

            if (feature.mReqFlags & !flags) != 0 {
                return Err(());
            }

            if range != nsMediaExpression_Range::eEqual &&
                feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
                return Err(());
            }

            // If there's no colon, this is a media query of the form
            // '(<feature>)', that is, there's no value specified.
            //
            // FIXME(emilio): We need to check for range operators too here when
            // we support them, see:
            //
            // https://drafts.csswg.org/mediaqueries/#mq-ranges
            if input.try(|i| i.expect_colon()).is_err() {
                return Ok(Expression::new(feature, None, range));
            }

            let value = match feature.mValueType {
                nsMediaFeature_ValueType::eLength => {
                    MediaExpressionValue::Length(
                        specified::Length::parse_non_negative(input)?)
                },
                nsMediaFeature_ValueType::eInteger => {
                    let i = input.expect_integer()?;
                    if i < 0 {
                        return Err(())
                    }
                    MediaExpressionValue::Integer(i as u32)
                }
                nsMediaFeature_ValueType::eBoolInteger => {
                    let i = input.expect_integer()?;
                    if i < 0 || i > 1 {
                        return Err(())
                    }
                    MediaExpressionValue::BoolInteger(i == 1)
                }
                nsMediaFeature_ValueType::eFloat => {
                    MediaExpressionValue::Float(input.expect_number()?)
                }
                nsMediaFeature_ValueType::eIntRatio => {
                    let a = input.expect_integer()?;
                    if a <= 0 {
                        return Err(())
                    }

                    input.expect_delim('/')?;

                    let b = input.expect_integer()?;
                    if b <= 0 {
                        return Err(())
                    }
                    MediaExpressionValue::IntRatio(a as u32, b as u32)
                }
                nsMediaFeature_ValueType::eResolution => {
                    MediaExpressionValue::Resolution(Resolution::parse(input)?)
                }
                nsMediaFeature_ValueType::eEnumerated => {
                    let index = unsafe {
                        let _table = feature.mData.mKeywordTable.as_ref();
                        // TODO
                        0
                    };
                    MediaExpressionValue::Enumerated(index)
                }
                nsMediaFeature_ValueType::eIdent => {
                    MediaExpressionValue::Ident(input.expect_ident()?.into())
                }
            };

            Ok(Expression::new(feature, value, range))
        })
    }

    /// Returns whether this media query evaluates to true for the given
    /// device.
    pub fn matches(&self, _device: &Device) -> bool {
        // TODO
        false
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Animated values.
//!
//! Some values, notably colors, cannot be interpolated directly with their
//! computed values and need yet another intermediate representation. This
//! module's raison d'être is to ultimately contain all these types.

use app_units::Au;
use euclid::{Point2D, Size2D};
use smallvec::SmallVec;
use std::cmp::max;
use values::computed::Angle as ComputedAngle;
use values::computed::BorderCornerRadius as ComputedBorderCornerRadius;
#[cfg(feature = "servo")]
use values::computed::ComputedUrl;
use values::computed::GreaterThanOrEqualToOneNumber as ComputedGreaterThanOrEqualToOneNumber;
use values::computed::MaxLength as ComputedMaxLength;
use values::computed::MozLength as ComputedMozLength;
use values::computed::NonNegativeAu;
use values::computed::NonNegativeLengthOrPercentage as ComputedNonNegativeLengthOrPercentage;
use values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
use values::computed::PositiveInteger as ComputedPositiveInteger;
use values::specified::url::SpecifiedUrl;

pub mod color;
pub mod effects;

/// Animate from one value to another.
///
/// This trait is derivable with `#[derive(Animate)]`. The derived
/// implementation uses a `match` expression with identical patterns for both
/// `self` and `other`, calling `Animate::animate` on each fields of the values.
/// If a field is annotated with `#[animation(constant)]`, the two values should
/// be equal or an error is returned.
///
/// If a variant is annotated with `#[animation(error)]`, the corresponding
/// `match` arm is not generated.
///
/// If the two values are not similar, an error is returned unless a fallback
/// function has been specified through `#[animate(fallback)]`.
pub trait Animate: Sized {
    /// Animate a value towards another one, given an animation procedure.
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>;
}

/// An animation procedure.
///
/// https://w3c.github.io/web-animations/#procedures-for-animating-properties
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Procedure {
    /// https://w3c.github.io/web-animations/#animation-interpolation
    Interpolate { progress: f64 },
    /// https://w3c.github.io/web-animations/#animation-addition
    Add,
    /// https://w3c.github.io/web-animations/#animation-accumulation
    Accumulate { count: u64 },
}

/// Conversion between computed values and intermediate values for animations.
///
/// Notably, colors are represented as four floats during animations.
///
/// This trait is derivable with `#[derive(ToAnimatedValue)]`.
pub trait ToAnimatedValue {
    /// The type of the animated value.
    type AnimatedValue;

    /// Converts this value to an animated value.
    fn to_animated_value(self) -> Self::AnimatedValue;

    /// Converts back an animated value into a computed value.
    fn from_animated_value(animated: Self::AnimatedValue) -> Self;
}

/// Marker trait for computed values with the same representation during animations.
pub trait AnimatedValueAsComputed {}

/// Returns a value similar to `self` that represents zero.
///
/// This trait is derivable with `#[derive(ToAnimatedValue)]`. If a field is
/// annotated with `#[animation(constant)]`, a clone of its value will be used
/// instead of calling `ToAnimatedZero::to_animated_zero` on it.
///
/// If a variant is annotated with `#[animation(error)]`, the corresponding
/// `match` arm is not generated.
pub trait ToAnimatedZero: Sized {
    /// Returns a value that, when added with an underlying value, will produce the underlying
    /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from
    /// the zero value to the 'by' value, and then adds the result to the underlying value.
    ///
    /// This is not the necessarily the same as the initial value of a property. For example, the
    /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the
    /// underlying value will not produce the underlying value.
    fn to_animated_zero(&self) -> Result<Self, ()>;
}

impl Procedure {
    /// Returns this procedure as a pair of weights.
    ///
    /// This is useful for animations that don't animate differently
    /// depending on the used procedure.
    #[inline]
    pub fn weights(self) -> (f64, f64) {
        match self {
            Procedure::Interpolate { progress } => (1. - progress, progress),
            Procedure::Add => (1., 1.),
            Procedure::Accumulate { count } => (count as f64, 1.),
        }
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for i32 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(((*self as f64).animate(&(*other as f64), procedure)? + 0.5).floor() as i32)
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for f32 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok((*self as f64).animate(&(*other as f64), procedure)? as f32)
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for f64 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        let (self_weight, other_weight) = procedure.weights();
        Ok(*self * self_weight + *other * other_weight)
    }
}

impl<T> Animate for Option<T>
where
    T: Animate,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        match (self.as_ref(), other.as_ref()) {
            (Some(ref this), Some(ref other)) => Ok(Some(this.animate(other, procedure)?)),
            (None, None) => Ok(None),
            _ => Err(()),
        }
    }
}

impl Animate for Au {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Au(self.0.animate(&other.0, procedure)?))
    }
}

impl<T> Animate for Size2D<T>
where
    T: Animate + Copy,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Size2D::new(
            self.width.animate(&other.width, procedure)?,
            self.height.animate(&other.height, procedure)?,
        ))
    }
}

impl<T> Animate for Point2D<T>
where
    T: Animate + Copy,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Point2D::new(
            self.x.animate(&other.x, procedure)?,
            self.y.animate(&other.y, procedure)?,
        ))
    }
}

impl<T> ToAnimatedValue for Option<T>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = Option<<T as ToAnimatedValue>::AnimatedValue>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.map(T::to_animated_value)
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.map(T::from_animated_value)
    }
}

impl<T> ToAnimatedValue for Vec<T>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = Vec<<T as ToAnimatedValue>::AnimatedValue>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.into_iter().map(T::to_animated_value).collect()
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.into_iter().map(T::from_animated_value).collect()
    }
}

impl<T> ToAnimatedValue for SmallVec<[T; 1]>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.into_iter().map(T::to_animated_value).collect()
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.into_iter().map(T::from_animated_value).collect()
    }
}

impl AnimatedValueAsComputed for Au {}
impl AnimatedValueAsComputed for ComputedAngle {}
impl AnimatedValueAsComputed for SpecifiedUrl {}
#[cfg(feature = "servo")]
impl AnimatedValueAsComputed for ComputedUrl {}
impl AnimatedValueAsComputed for bool {}
impl AnimatedValueAsComputed for f32 {}

impl<T> ToAnimatedValue for T
where
    T: AnimatedValueAsComputed,
{
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated
    }
}

impl ToAnimatedValue for ComputedNonNegativeNumber {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.max(0.).into()
    }
}

impl ToAnimatedValue for ComputedGreaterThanOrEqualToOneNumber {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.max(1.).into()
    }
}

impl ToAnimatedValue for NonNegativeAu {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        max(animated.0, Au(0)).into()
    }
}

impl ToAnimatedValue for ComputedPositiveInteger {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        max(animated.0, 1).into()
    }
}

impl ToAnimatedValue for ComputedNonNegativeLengthOrPercentage {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.clamp_to_non_negative().into()
    }
}

impl ToAnimatedValue for ComputedBorderCornerRadius {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        ComputedBorderCornerRadius::new(animated.0.width.clamp_to_non_negative(),
                                        animated.0.height.clamp_to_non_negative())
    }
}

impl ToAnimatedValue for ComputedMaxLength {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        use values::computed::{LengthOrPercentageOrNone, Percentage};
        match animated {
            ComputedMaxLength::LengthOrPercentageOrNone(lopn) => {
                let result = match lopn {
                    LengthOrPercentageOrNone::Length(au) => {
                        LengthOrPercentageOrNone::Length(max(au, Au(0)))
                    },
                    LengthOrPercentageOrNone::Percentage(percentage) => {
                        LengthOrPercentageOrNone::Percentage(Percentage(percentage.0.max(0.)))
                    }
                    _ => lopn
                };
                ComputedMaxLength::LengthOrPercentageOrNone(result)
            },
            _ => animated
        }
    }
}

impl ToAnimatedValue for ComputedMozLength {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        use values::computed::{LengthOrPercentageOrAuto, Percentage};
        match animated {
            ComputedMozLength::LengthOrPercentageOrAuto(lopa) => {
                let result = match lopa {
                    LengthOrPercentageOrAuto::Length(au) => {
                        LengthOrPercentageOrAuto::Length(max(au, Au(0)))
                    },
                    LengthOrPercentageOrAuto::Percentage(percentage) => {
                        LengthOrPercentageOrAuto::Percentage(Percentage(percentage.0.max(0.)))
                    }
                    _ => lopa
                };
                ComputedMozLength::LengthOrPercentageOrAuto(result)
            },
            _ => animated
        }
    }
}

impl ToAnimatedZero for Au {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(Au(0)) }
}

impl ToAnimatedZero for f32 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0.) }
}

impl ToAnimatedZero for f64 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0.) }
}

impl ToAnimatedZero for i32 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0) }
}

impl<T> ToAnimatedZero for Option<T>
where
    T: ToAnimatedZero,
{
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> {
        match *self {
            Some(ref value) => Ok(Some(value.to_animated_zero()?)),
            None => Ok(None),
        }
    }
}
<commit_msg>Auto merge of #18292 - emilio:animate-nan, r=nox<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Animated values.
//!
//! Some values, notably colors, cannot be interpolated directly with their
//! computed values and need yet another intermediate representation. This
//! module's raison d'être is to ultimately contain all these types.

use app_units::Au;
use euclid::{Point2D, Size2D};
use smallvec::SmallVec;
use std::cmp::max;
use values::computed::Angle as ComputedAngle;
use values::computed::BorderCornerRadius as ComputedBorderCornerRadius;
#[cfg(feature = "servo")]
use values::computed::ComputedUrl;
use values::computed::GreaterThanOrEqualToOneNumber as ComputedGreaterThanOrEqualToOneNumber;
use values::computed::MaxLength as ComputedMaxLength;
use values::computed::MozLength as ComputedMozLength;
use values::computed::NonNegativeAu;
use values::computed::NonNegativeLengthOrPercentage as ComputedNonNegativeLengthOrPercentage;
use values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
use values::computed::PositiveInteger as ComputedPositiveInteger;
use values::specified::url::SpecifiedUrl;

pub mod color;
pub mod effects;

/// Animate from one value to another.
///
/// This trait is derivable with `#[derive(Animate)]`. The derived
/// implementation uses a `match` expression with identical patterns for both
/// `self` and `other`, calling `Animate::animate` on each fields of the values.
/// If a field is annotated with `#[animation(constant)]`, the two values should
/// be equal or an error is returned.
///
/// If a variant is annotated with `#[animation(error)]`, the corresponding
/// `match` arm is not generated.
///
/// If the two values are not similar, an error is returned unless a fallback
/// function has been specified through `#[animate(fallback)]`.
pub trait Animate: Sized {
    /// Animate a value towards another one, given an animation procedure.
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>;
}

/// An animation procedure.
///
/// https://w3c.github.io/web-animations/#procedures-for-animating-properties
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Procedure {
    /// https://w3c.github.io/web-animations/#animation-interpolation
    Interpolate { progress: f64 },
    /// https://w3c.github.io/web-animations/#animation-addition
    Add,
    /// https://w3c.github.io/web-animations/#animation-accumulation
    Accumulate { count: u64 },
}

/// Conversion between computed values and intermediate values for animations.
///
/// Notably, colors are represented as four floats during animations.
///
/// This trait is derivable with `#[derive(ToAnimatedValue)]`.
pub trait ToAnimatedValue {
    /// The type of the animated value.
    type AnimatedValue;

    /// Converts this value to an animated value.
    fn to_animated_value(self) -> Self::AnimatedValue;

    /// Converts back an animated value into a computed value.
    fn from_animated_value(animated: Self::AnimatedValue) -> Self;
}

/// Marker trait for computed values with the same representation during animations.
pub trait AnimatedValueAsComputed {}

/// Returns a value similar to `self` that represents zero.
///
/// This trait is derivable with `#[derive(ToAnimatedValue)]`. If a field is
/// annotated with `#[animation(constant)]`, a clone of its value will be used
/// instead of calling `ToAnimatedZero::to_animated_zero` on it.
///
/// If a variant is annotated with `#[animation(error)]`, the corresponding
/// `match` arm is not generated.
pub trait ToAnimatedZero: Sized {
    /// Returns a value that, when added with an underlying value, will produce the underlying
    /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from
    /// the zero value to the 'by' value, and then adds the result to the underlying value.
    ///
    /// This is not the necessarily the same as the initial value of a property. For example, the
    /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the
    /// underlying value will not produce the underlying value.
    fn to_animated_zero(&self) -> Result<Self, ()>;
}

impl Procedure {
    /// Returns this procedure as a pair of weights.
    ///
    /// This is useful for animations that don't animate differently
    /// depending on the used procedure.
    #[inline]
    pub fn weights(self) -> (f64, f64) {
        match self {
            Procedure::Interpolate { progress } => (1. - progress, progress),
            Procedure::Add => (1., 1.),
            Procedure::Accumulate { count } => (count as f64, 1.),
        }
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for i32 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(((*self as f64).animate(&(*other as f64), procedure)? + 0.5).floor() as i32)
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for f32 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        use std::f32;

        let ret = (*self as f64).animate(&(*other as f64), procedure)?;
        Ok(ret.min(f32::MAX as f64).max(f32::MIN as f64) as f32)
    }
}

/// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animate for f64 {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        use std::f64;

        let (self_weight, other_weight) = procedure.weights();

        let ret = *self * self_weight + *other * other_weight;
        Ok(ret.min(f64::MAX).max(f64::MIN))
    }
}

impl<T> Animate for Option<T>
where
    T: Animate,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        match (self.as_ref(), other.as_ref()) {
            (Some(ref this), Some(ref other)) => Ok(Some(this.animate(other, procedure)?)),
            (None, None) => Ok(None),
            _ => Err(()),
        }
    }
}

impl Animate for Au {
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Au(self.0.animate(&other.0, procedure)?))
    }
}

impl<T> Animate for Size2D<T>
where
    T: Animate + Copy,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Size2D::new(
            self.width.animate(&other.width, procedure)?,
            self.height.animate(&other.height, procedure)?,
        ))
    }
}

impl<T> Animate for Point2D<T>
where
    T: Animate + Copy,
{
    #[inline]
    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Point2D::new(
            self.x.animate(&other.x, procedure)?,
            self.y.animate(&other.y, procedure)?,
        ))
    }
}

impl<T> ToAnimatedValue for Option<T>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = Option<<T as ToAnimatedValue>::AnimatedValue>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.map(T::to_animated_value)
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.map(T::from_animated_value)
    }
}

impl<T> ToAnimatedValue for Vec<T>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = Vec<<T as ToAnimatedValue>::AnimatedValue>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.into_iter().map(T::to_animated_value).collect()
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.into_iter().map(T::from_animated_value).collect()
    }
}

impl<T> ToAnimatedValue for SmallVec<[T; 1]>
where
    T: ToAnimatedValue,
{
    type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>;

    #[inline]
    fn to_animated_value(self) -> Self::AnimatedValue {
        self.into_iter().map(T::to_animated_value).collect()
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.into_iter().map(T::from_animated_value).collect()
    }
}

impl AnimatedValueAsComputed for Au {}
impl AnimatedValueAsComputed for ComputedAngle {}
impl AnimatedValueAsComputed for SpecifiedUrl {}
#[cfg(feature = "servo")]
impl AnimatedValueAsComputed for ComputedUrl {}
impl AnimatedValueAsComputed for bool {}
impl AnimatedValueAsComputed for f32 {}

impl<T> ToAnimatedValue for T
where
    T: AnimatedValueAsComputed,
{
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated
    }
}

impl ToAnimatedValue for ComputedNonNegativeNumber {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.max(0.).into()
    }
}

impl ToAnimatedValue for ComputedGreaterThanOrEqualToOneNumber {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.max(1.).into()
    }
}

impl ToAnimatedValue for NonNegativeAu {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        max(animated.0, Au(0)).into()
    }
}

impl ToAnimatedValue for ComputedPositiveInteger {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        max(animated.0, 1).into()
    }
}

impl ToAnimatedValue for ComputedNonNegativeLengthOrPercentage {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        animated.0.clamp_to_non_negative().into()
    }
}

impl ToAnimatedValue for ComputedBorderCornerRadius {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        ComputedBorderCornerRadius::new(animated.0.width.clamp_to_non_negative(),
                                        animated.0.height.clamp_to_non_negative())
    }
}

impl ToAnimatedValue for ComputedMaxLength {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        use values::computed::{LengthOrPercentageOrNone, Percentage};
        match animated {
            ComputedMaxLength::LengthOrPercentageOrNone(lopn) => {
                let result = match lopn {
                    LengthOrPercentageOrNone::Length(au) => {
                        LengthOrPercentageOrNone::Length(max(au, Au(0)))
                    },
                    LengthOrPercentageOrNone::Percentage(percentage) => {
                        LengthOrPercentageOrNone::Percentage(Percentage(percentage.0.max(0.)))
                    }
                    _ => lopn
                };
                ComputedMaxLength::LengthOrPercentageOrNone(result)
            },
            _ => animated
        }
    }
}

impl ToAnimatedValue for ComputedMozLength {
    type AnimatedValue = Self;

    #[inline]
    fn to_animated_value(self) -> Self {
        self
    }

    #[inline]
    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
        use values::computed::{LengthOrPercentageOrAuto, Percentage};
        match animated {
            ComputedMozLength::LengthOrPercentageOrAuto(lopa) => {
                let result = match lopa {
                    LengthOrPercentageOrAuto::Length(au) => {
                        LengthOrPercentageOrAuto::Length(max(au, Au(0)))
                    },
                    LengthOrPercentageOrAuto::Percentage(percentage) => {
                        LengthOrPercentageOrAuto::Percentage(Percentage(percentage.0.max(0.)))
                    }
                    _ => lopa
                };
                ComputedMozLength::LengthOrPercentageOrAuto(result)
            },
            _ => animated
        }
    }
}

impl ToAnimatedZero for Au {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(Au(0)) }
}

impl ToAnimatedZero for f32 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0.) }
}

impl ToAnimatedZero for f64 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0.) }
}

impl ToAnimatedZero for i32 {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> { Ok(0) }
}

impl<T> ToAnimatedZero for Option<T>
where
    T: ToAnimatedZero,
{
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> {
        match *self {
            Some(ref value) => Ok(Some(value.to_animated_zero()?)),
            None => Ok(None),
        }
    }
}
<|endoftext|>


<commit_before>use byteorder::{ByteOrder, LittleEndian, NetworkEndian};
use bytes::{BufMut, Bytes, BytesMut};
use log::debug;
use rand;

use crate::ws::mask::apply_mask;
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
use crate::ws::ProtocolError;

/// A struct representing a `WebSocket` frame.
#[derive(Debug)]
pub struct Parser;

impl Parser {
    fn parse_metadata(
        src: &[u8],
        server: bool,
        max_size: usize,
    ) -> Result<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError> {
        let chunk_len = src.len();

        let mut idx = 2;
        if chunk_len < 2 {
            return Ok(None);
        }

        let first = src[0];
        let second = src[1];
        let finished = first & 0x80 != 0;

        // check masking
        let masked = second & 0x80 != 0;
        if !masked && server {
            return Err(ProtocolError::UnmaskedFrame);
        } else if masked && !server {
            return Err(ProtocolError::MaskedFrame);
        }

        // Op code
        let opcode = OpCode::from(first & 0x0F);

        if let OpCode::Bad = opcode {
            return Err(ProtocolError::InvalidOpcode(first & 0x0F));
        }

        let len = second & 0x7F;
        let length = if len == 126 {
            if chunk_len < 4 {
                return Ok(None);
            }
            let len = NetworkEndian::read_uint(&src[idx..], 2) as usize;
            idx += 2;
            len
        } else if len == 127 {
            if chunk_len < 10 {
                return Ok(None);
            }
            let len = NetworkEndian::read_uint(&src[idx..], 8);
            if len > max_size as u64 {
                return Err(ProtocolError::Overflow);
            }
            idx += 8;
            len as usize
        } else {
            len as usize
        };

        // check for max allowed size
        if length > max_size {
            return Err(ProtocolError::Overflow);
        }

        let mask = if server {
            if chunk_len < idx + 4 {
                return Ok(None);
            }

            let mask: &[u8] = &src[idx..idx + 4];
            let mask_u32 = LittleEndian::read_u32(mask);
            idx += 4;
            Some(mask_u32)
        } else {
            None
        };

        Ok(Some((idx, finished, opcode, length, mask)))
    }

    /// Parse the input stream into a frame.
    pub fn parse(
        src: &mut BytesMut,
        server: bool,
        max_size: usize,
    ) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> {
        // try to parse ws frame metadata
        let (idx, finished, opcode, length, mask) =
            match Parser::parse_metadata(src, server, max_size)? {
                None => return Ok(None),
                Some(res) => res,
            };

        // not enough data
        if src.len() < idx + length {
            return Ok(None);
        }

        // remove prefix
        src.split_to(idx);

        // no need for body
        if length == 0 {
            return Ok(Some((finished, opcode, None)));
        }

        let mut data = src.split_to(length);

        // control frames must have length <= 125
        match opcode {
            OpCode::Ping | OpCode::Pong if length > 125 => {
                return Err(ProtocolError::InvalidLength(length));
            }
            OpCode::Close if length > 125 => {
                debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
                return Ok(Some((true, OpCode::Close, None)));
            }
            _ => (),
        }

        // unmask
        if let Some(mask) = mask {
            apply_mask(&mut data, mask);
        }

        Ok(Some((finished, opcode, Some(data))))
    }

    /// Parse the payload of a close frame.
    pub fn parse_close_payload(payload: &[u8]) -> Option<CloseReason> {
        if payload.len() >= 2 {
            let raw_code = NetworkEndian::read_u16(payload);
            let code = CloseCode::from(raw_code);
            let description = if payload.len() > 2 {
                Some(String::from_utf8_lossy(&payload[2..]).into())
            } else {
                None
            };
            Some(CloseReason { code, description })
        } else {
            None
        }
    }

    /// Generate binary representation
    pub fn write_message<B: Into<Bytes>>(
        dst: &mut BytesMut,
        pl: B,
        op: OpCode,
        fin: bool,
        mask: bool,
    ) {
        let payload = pl.into();
        let one: u8 = if fin {
            0x80 | Into::<u8>::into(op)
        } else {
            op.into()
        };
        let payload_len = payload.len();
        let (two, p_len) = if mask {
            (0x80, payload_len + 4)
        } else {
            (0, payload_len)
        };

        if payload_len < 126 {
            dst.put_slice(&[one, two | payload_len as u8]);
        } else if payload_len <= 65_535 {
            dst.reserve(p_len + 4);
            dst.put_slice(&[one, two | 126]);
            dst.put_u16_be(payload_len as u16);
        } else {
            dst.reserve(p_len + 10);
            dst.put_slice(&[one, two | 127]);
            dst.put_u64_be(payload_len as u64);
        };

        if mask {
            let mask = rand::random::<u32>();
            dst.put_u32_le(mask);
            dst.extend_from_slice(payload.as_ref());
            let pos = dst.len() - payload_len;
            apply_mask(&mut dst[pos..], mask);
        } else {
            dst.put_slice(payload.as_ref());
        }
    }

    /// Create a new Close control frame.
    #[inline]
    pub fn write_close(dst: &mut BytesMut, reason: Option<CloseReason>, mask: bool) {
        let payload = match reason {
            None => Vec::new(),
            Some(reason) => {
                let mut code_bytes = [0; 2];
                NetworkEndian::write_u16(&mut code_bytes, reason.code.into());

                let mut payload = Vec::from(&code_bytes[..]);
                if let Some(description) = reason.description {
                    payload.extend(description.as_bytes());
                }
                payload
            }
        };

        Parser::write_message(dst, payload, OpCode::Close, true, mask)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytes::Bytes;

    struct F {
        finished: bool,
        opcode: OpCode,
        payload: Bytes,
    }

    fn is_none(
        frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
    ) -> bool {
        match *frm {
            Ok(None) => true,
            _ => false,
        }
    }

    fn extract(
        frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
    ) -> F {
        match frm {
            Ok(Some((finished, opcode, payload))) => F {
                finished,
                opcode,
                payload: payload
                    .map(|b| b.freeze())
                    .unwrap_or_else(|| Bytes::from("")),
            },
            _ => unreachable!("error"),
        }
    }

    #[test]
    fn test_parse() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        buf.extend(b"1");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1"[..]);
    }

    #[test]
    fn test_parse_length0() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert!(frame.payload.is_empty());
    }

    #[test]
    fn test_parse_length2() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
        buf.extend(&[0u8, 4u8][..]);
        buf.extend(b"1234");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
    }

    #[test]
    fn test_parse_length4() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
        buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
        buf.extend(b"1234");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
    }

    #[test]
    fn test_parse_frame_mask() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
        buf.extend(b"0001");
        buf.extend(b"1");

        assert!(Parser::parse(&mut buf, false, 1024).is_err());

        let frame = extract(Parser::parse(&mut buf, true, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
    }

    #[test]
    fn test_parse_frame_no_mask() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        buf.extend(&[1u8]);

        assert!(Parser::parse(&mut buf, true, 1024).is_err());

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
    }

    #[test]
    fn test_parse_frame_max_size() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
        buf.extend(&[1u8, 1u8]);

        assert!(Parser::parse(&mut buf, true, 1).is_err());

        if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) {
        } else {
            unreachable!("error");
        }
    }

    #[test]
    fn test_ping_frame() {
        let mut buf = BytesMut::new();
        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false);

        let mut v = vec![137u8, 4u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_pong_frame() {
        let mut buf = BytesMut::new();
        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false);

        let mut v = vec![138u8, 4u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_close_frame() {
        let mut buf = BytesMut::new();
        let reason = (CloseCode::Normal, "data");
        Parser::write_close(&mut buf, Some(reason.into()), false);

        let mut v = vec![136u8, 6u8, 3u8, 232u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_empty_close_frame() {
        let mut buf = BytesMut::new();
        Parser::write_close(&mut buf, None, false);
        assert_eq!(&buf[..], &vec![0x88, 0x00][..]);
    }
}
<commit_msg>allocate enough space<commit_after>use byteorder::{ByteOrder, LittleEndian, NetworkEndian};
use bytes::{BufMut, Bytes, BytesMut};
use log::debug;
use rand;

use crate::ws::mask::apply_mask;
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
use crate::ws::ProtocolError;

/// A struct representing a `WebSocket` frame.
#[derive(Debug)]
pub struct Parser;

impl Parser {
    fn parse_metadata(
        src: &[u8],
        server: bool,
        max_size: usize,
    ) -> Result<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError> {
        let chunk_len = src.len();

        let mut idx = 2;
        if chunk_len < 2 {
            return Ok(None);
        }

        let first = src[0];
        let second = src[1];
        let finished = first & 0x80 != 0;

        // check masking
        let masked = second & 0x80 != 0;
        if !masked && server {
            return Err(ProtocolError::UnmaskedFrame);
        } else if masked && !server {
            return Err(ProtocolError::MaskedFrame);
        }

        // Op code
        let opcode = OpCode::from(first & 0x0F);

        if let OpCode::Bad = opcode {
            return Err(ProtocolError::InvalidOpcode(first & 0x0F));
        }

        let len = second & 0x7F;
        let length = if len == 126 {
            if chunk_len < 4 {
                return Ok(None);
            }
            let len = NetworkEndian::read_uint(&src[idx..], 2) as usize;
            idx += 2;
            len
        } else if len == 127 {
            if chunk_len < 10 {
                return Ok(None);
            }
            let len = NetworkEndian::read_uint(&src[idx..], 8);
            if len > max_size as u64 {
                return Err(ProtocolError::Overflow);
            }
            idx += 8;
            len as usize
        } else {
            len as usize
        };

        // check for max allowed size
        if length > max_size {
            return Err(ProtocolError::Overflow);
        }

        let mask = if server {
            if chunk_len < idx + 4 {
                return Ok(None);
            }

            let mask: &[u8] = &src[idx..idx + 4];
            let mask_u32 = LittleEndian::read_u32(mask);
            idx += 4;
            Some(mask_u32)
        } else {
            None
        };

        Ok(Some((idx, finished, opcode, length, mask)))
    }

    /// Parse the input stream into a frame.
    pub fn parse(
        src: &mut BytesMut,
        server: bool,
        max_size: usize,
    ) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> {
        // try to parse ws frame metadata
        let (idx, finished, opcode, length, mask) =
            match Parser::parse_metadata(src, server, max_size)? {
                None => return Ok(None),
                Some(res) => res,
            };

        // not enough data
        if src.len() < idx + length {
            return Ok(None);
        }

        // remove prefix
        src.split_to(idx);

        // no need for body
        if length == 0 {
            return Ok(Some((finished, opcode, None)));
        }

        let mut data = src.split_to(length);

        // control frames must have length <= 125
        match opcode {
            OpCode::Ping | OpCode::Pong if length > 125 => {
                return Err(ProtocolError::InvalidLength(length));
            }
            OpCode::Close if length > 125 => {
                debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
                return Ok(Some((true, OpCode::Close, None)));
            }
            _ => (),
        }

        // unmask
        if let Some(mask) = mask {
            apply_mask(&mut data, mask);
        }

        Ok(Some((finished, opcode, Some(data))))
    }

    /// Parse the payload of a close frame.
    pub fn parse_close_payload(payload: &[u8]) -> Option<CloseReason> {
        if payload.len() >= 2 {
            let raw_code = NetworkEndian::read_u16(payload);
            let code = CloseCode::from(raw_code);
            let description = if payload.len() > 2 {
                Some(String::from_utf8_lossy(&payload[2..]).into())
            } else {
                None
            };
            Some(CloseReason { code, description })
        } else {
            None
        }
    }

    /// Generate binary representation
    pub fn write_message<B: Into<Bytes>>(
        dst: &mut BytesMut,
        pl: B,
        op: OpCode,
        fin: bool,
        mask: bool,
    ) {
        let payload = pl.into();
        let one: u8 = if fin {
            0x80 | Into::<u8>::into(op)
        } else {
            op.into()
        };
        let payload_len = payload.len();
        let (two, p_len) = if mask {
            (0x80, payload_len + 4)
        } else {
            (0, payload_len)
        };

        if payload_len < 126 {
            dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
            dst.put_slice(&[one, two | payload_len as u8]);
        } else if payload_len <= 65_535 {
            dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
            dst.put_slice(&[one, two | 126]);
            dst.put_u16_be(payload_len as u16);
        } else {
            dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
            dst.put_slice(&[one, two | 127]);
            dst.put_u64_be(payload_len as u64);
        };

        if mask {
            let mask = rand::random::<u32>();
            dst.put_u32_le(mask);
            dst.put_slice(payload.as_ref());
            let pos = dst.len() - payload_len;
            apply_mask(&mut dst[pos..], mask);
        } else {
            dst.put_slice(payload.as_ref());
        }
    }

    /// Create a new Close control frame.
    #[inline]
    pub fn write_close(dst: &mut BytesMut, reason: Option<CloseReason>, mask: bool) {
        let payload = match reason {
            None => Vec::new(),
            Some(reason) => {
                let mut code_bytes = [0; 2];
                NetworkEndian::write_u16(&mut code_bytes, reason.code.into());

                let mut payload = Vec::from(&code_bytes[..]);
                if let Some(description) = reason.description {
                    payload.extend(description.as_bytes());
                }
                payload
            }
        };

        Parser::write_message(dst, payload, OpCode::Close, true, mask)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytes::Bytes;

    struct F {
        finished: bool,
        opcode: OpCode,
        payload: Bytes,
    }

    fn is_none(
        frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
    ) -> bool {
        match *frm {
            Ok(None) => true,
            _ => false,
        }
    }

    fn extract(
        frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
    ) -> F {
        match frm {
            Ok(Some((finished, opcode, payload))) => F {
                finished,
                opcode,
                payload: payload
                    .map(|b| b.freeze())
                    .unwrap_or_else(|| Bytes::from("")),
            },
            _ => unreachable!("error"),
        }
    }

    #[test]
    fn test_parse() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        buf.extend(b"1");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1"[..]);
    }

    #[test]
    fn test_parse_length0() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert!(frame.payload.is_empty());
    }

    #[test]
    fn test_parse_length2() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
        buf.extend(&[0u8, 4u8][..]);
        buf.extend(b"1234");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
    }

    #[test]
    fn test_parse_length4() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));

        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
        buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
        buf.extend(b"1234");

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
    }

    #[test]
    fn test_parse_frame_mask() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
        buf.extend(b"0001");
        buf.extend(b"1");

        assert!(Parser::parse(&mut buf, false, 1024).is_err());

        let frame = extract(Parser::parse(&mut buf, true, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
    }

    #[test]
    fn test_parse_frame_no_mask() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
        buf.extend(&[1u8]);

        assert!(Parser::parse(&mut buf, true, 1024).is_err());

        let frame = extract(Parser::parse(&mut buf, false, 1024));
        assert!(!frame.finished);
        assert_eq!(frame.opcode, OpCode::Text);
        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
    }

    #[test]
    fn test_parse_frame_max_size() {
        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
        buf.extend(&[1u8, 1u8]);

        assert!(Parser::parse(&mut buf, true, 1).is_err());

        if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) {
        } else {
            unreachable!("error");
        }
    }

    #[test]
    fn test_ping_frame() {
        let mut buf = BytesMut::new();
        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false);

        let mut v = vec![137u8, 4u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_pong_frame() {
        let mut buf = BytesMut::new();
        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false);

        let mut v = vec![138u8, 4u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_close_frame() {
        let mut buf = BytesMut::new();
        let reason = (CloseCode::Normal, "data");
        Parser::write_close(&mut buf, Some(reason.into()), false);

        let mut v = vec![136u8, 6u8, 3u8, 232u8];
        v.extend(b"data");
        assert_eq!(&buf[..], &v[..]);
    }

    #[test]
    fn test_empty_close_frame() {
        let mut buf = BytesMut::new();
        Parser::write_close(&mut buf, None, false);
        assert_eq!(&buf[..], &vec![0x88, 0x00][..]);
    }
}
<|endoftext|>


<commit_before>use regex::Regex;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let weak_linkage = match env::var("CARGO_FEATURE_WEAK_LINKAGE") {
        Ok(_) => true,
        Err(_) => false,
    };

    // Generate C++ bindings
    let mut build_config = cpp_build::Config::new();
    build_config.include("include");
    if weak_linkage {
        build_config.cpp_link_stdlib(None);
    }

    build_config.build("src/lldb.rs");
    for entry in fs::read_dir("src").unwrap() {
        println!("cargo:rerun-if-changed={}", entry.unwrap().path().display());
    }

    if target_os == "windows" {
        strong_linkage();
    } else {
        if weak_linkage {
            if target_os == "macos" {
                println!("cargo:rustc-cdylib-link-arg=-undefined");
                println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
            }
        } else {
            strong_linkage();
        }
    }
}

fn strong_linkage() {
    // Find CMakeCache
    let mut path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let cmakecache = loop {
        let f = path.with_file_name("CMakeCache.txt");
        if f.is_file() {
            break f;
        }
        if !path.pop() {
            println!("cargo:warning=Could not find CMakeCache.txt");
            return;
        }
    };
    println!("cargo:rerun-if-changed={}", cmakecache.display());
    let config = parse_cmakecache(&cmakecache);

    if let Some(value) = config.get("LLDB_LinkSearch") {
        for path in value.split_terminator(';') {
            println!("cargo:rustc-link-search=native={}", path);
        }
    } else {
        println!("cargo:warning=LLDB_LinkSearch not set");
    }

    if let Some(value) = config.get("LLDB_LinkDylib") {
        for path in value.split_terminator(';') {
            println!("cargo:rustc-link-lib=dylib={}", path);
        }
    } else {
        println!("cargo:warning=LLDB_LinkDylib not set");
    }
}

fn parse_cmakecache(cmakecache: &Path) -> HashMap<String, String> {
    let mut result = HashMap::new();
    let reader = BufReader::new(File::open(cmakecache).expect("Open file"));
    let kvregex = Regex::new("(?mx) ^ ([A-Za-z_]+) : (STRING|FILEPATH|BOOL|STATIC|INTERNAL) = (.*)").unwrap();

    for line in reader.lines() {
        let line = line.unwrap();
        if let Some(captures) = kvregex.captures(&line) {
            let key = captures.get(1).expect("Invalid format").as_str();
            let value = captures.get(3).expect("Invalid format").as_str();
            result.insert(key.into(), value.into());
        }
    }
    result
}
<commit_msg>Build cpp in debug.<commit_after>use regex::Regex;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let weak_linkage = match env::var("CARGO_FEATURE_WEAK_LINKAGE") {
        Ok(_) => true,
        Err(_) => false,
    };

    // Generate C++ bindings
    let mut build_config = cpp_build::Config::new();
    build_config.include("include");
    build_config.debug(true);
    if weak_linkage {
        build_config.cpp_link_stdlib(None);
    }

    build_config.build("src/lldb.rs");
    for entry in fs::read_dir("src").unwrap() {
        println!("cargo:rerun-if-changed={}", entry.unwrap().path().display());
    }

    if target_os == "windows" {
        strong_linkage();
    } else {
        if weak_linkage {
            if target_os == "macos" {
                println!("cargo:rustc-cdylib-link-arg=-undefined");
                println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
            }
        } else {
            strong_linkage();
        }
    }
}

fn strong_linkage() {
    // Find CMakeCache
    let mut path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let cmakecache = loop {
        let f = path.with_file_name("CMakeCache.txt");
        if f.is_file() {
            break f;
        }
        if !path.pop() {
            println!("cargo:warning=Could not find CMakeCache.txt");
            return;
        }
    };
    println!("cargo:rerun-if-changed={}", cmakecache.display());
    let config = parse_cmakecache(&cmakecache);

    if let Some(value) = config.get("LLDB_LinkSearch") {
        for path in value.split_terminator(';') {
            println!("cargo:rustc-link-search=native={}", path);
        }
    } else {
        println!("cargo:warning=LLDB_LinkSearch not set");
    }

    if let Some(value) = config.get("LLDB_LinkDylib") {
        for path in value.split_terminator(';') {
            println!("cargo:rustc-link-lib=dylib={}", path);
        }
    } else {
        println!("cargo:warning=LLDB_LinkDylib not set");
    }
}

fn parse_cmakecache(cmakecache: &Path) -> HashMap<String, String> {
    let mut result = HashMap::new();
    let reader = BufReader::new(File::open(cmakecache).expect("Open file"));
    let kvregex = Regex::new("(?mx) ^ ([A-Za-z_]+) : (STRING|FILEPATH|BOOL|STATIC|INTERNAL) = (.*)").unwrap();

    for line in reader.lines() {
        let line = line.unwrap();
        if let Some(captures) = kvregex.captures(&line) {
            let key = captures.get(1).expect("Invalid format").as_str();
            let value = captures.get(3).expect("Invalid format").as_str();
            result.insert(key.into(), value.into());
        }
    }
    result
}
<|endoftext|>