<commit_before>use std::fmt;

use failure::{Fail};

#[derive(Debug, Fail)]
pub enum Error {
    #[fail(display = "Not an instruction")]
    NotAnInstruction,
    #[fail(display = "Instruction too long")]
    LongInstruction,
}

/// FlowMeta is metadata that describes code flow for an instruction.
///
/// The metadata is packed into a single byte, with the expectation that,
///  there may be one `FlowMeta` instance for each (code) offset in a program.
/// However, the you should not be worried about the representation, just the interface.
#[derive(Debug, Clone, Copy)]
pub struct FlowMeta(u8);

impl FlowMeta {
    // layout:
    //
    //     +-+-+-+-+-+-+-+-+
    //     |A|B|C|D|   E   |
    //     +-+-+-+-+-+-+-+-+
    //
    //  A - has xrefs to
    //  B - has xrefs from
    //  C - does other fallthrough to
    //  D - does fallthrough
    //  E - insn size

    pub fn zero() -> FlowMeta {
        FlowMeta(0x0)
    }

    /// Fetch the cached length of the instruction.
    ///
    /// If longer than 14 bytes, then will return `Error::LongInstruction` and you'll have to decode
    ///  the instruction yourself.
    ///
    /// Errors:
    ///   - NotAnInstruction
    ///   - LongInstruction
    pub fn get_insn_length(self) -> Result<u8, Error> {
        let v = self.0 & 0b0000_1111;
        match v {
            0x00 => Err(Error::NotAnInstruction),
            0x0F => Err(Error::LongInstruction),
            v => Ok(v),
        }
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    ///
    /// m.set_insn_length(0);
    /// assert_eq!(m.is_insn(), false);
    /// assert!(matches!(m.get_insn_length().err().unwrap(), Error::NotAnInstruction));
    ///
    /// m.set_insn_length(1);
    /// assert_eq!(m.is_insn(), true);
    /// assert_eq!(m.get_insn_length().unwrap(), 0x1);
    ///
    /// m.set_insn_length(2);
    /// assert_eq!(m.get_insn_length().unwrap(), 0x2);
    ///
    /// m.set_insn_length(0xE);
    /// assert_eq!(m.get_insn_length().unwrap(), 0xE);
    ///
    /// m.set_insn_length(0xF);
    /// assert_eq!(m.is_insn(), true);
    /// assert!(matches!(m.get_insn_length().err().unwrap(), Error::LongInstruction));
    /// ```
    pub fn set_insn_length(&mut self, length: u8) {
        let len = if length >= 0xF {
            // instrunction length too long
            0x0F
        } else if length == 0x0 {
            // not an instruction
            0x00
        } else {
            // good flow
            length & 0x0F
        };

        self.0 = (self.0 & 0b1111_0000) | len;
    }

    pub fn is_insn(self) -> bool {
        self.0 & 0b0000_1111 != 0
    }

    /// Does the instruction fallthrough?
    pub fn does_fallthrough(self) -> bool {
        self.0 & 0b0001_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.does_fallthrough(), false);
    ///
    /// m.set_fallthrough();
    /// assert_eq!(m.does_fallthrough(), true);
    /// ```
    pub fn set_fallthrough(&mut self) {
        self.0 |= 0b0001_0000;
    }

    /// Does another instruction fallthrough to this instruction?
    pub fn does_other_fallthrough_to(self) -> bool {
        self.0 & 0b0010_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.does_other_fallthrough_to(), false);
    ///
    /// m.set_other_fallthrough_to();
    /// assert_eq!(m.does_other_fallthrough_to(), true);
    /// ```
    pub fn set_other_fallthrough_to(&mut self) {
        self.0 |= 0b0010_0000;
    }

    /// Does the instruction have flow xrefs from it?
    /// This does not include the fallthrough flow.
    pub fn has_xrefs_from(self) -> bool {
        self.0 & 0b0100_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_from(), false);
    ///
    /// m.set_xrefs_from();
    /// assert_eq!(m.has_xrefs_from(), true);
    /// ```
    pub fn set_xrefs_from(&mut self) {
        self.0 |= 0b0100_0000;
    }

    /// Does the instruction have flow xrefs to it?
    pub fn has_xrefs_to(self) -> bool {
        self.0 & 0b1000_0000 > 0
    }

    /// Set the bit indicating that there are flow xrefs to this instruction.
    ///
    /// ```
    /// use lancelot::flowmeta::*;
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_to(), false);
    ///
    /// m.set_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), true);
    /// ```
    pub fn set_xrefs_to(&mut self) {
        self.0 |= 0b1000_0000;
    }

    /// Unset the bit indicating that there are flow xrefs to this instruction.
    ///
    /// ```
    /// use lancelot::flowmeta::*;
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_to(), false);
    ///
    /// m.set_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), true);
    ///
    /// m.unset_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), false);
    /// ```
    pub fn unset_xrefs_to(&mut self) {
        self.0 &= 0b0111_1111
    }
}

impl fmt::Display for FlowMeta {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FlowMeta{{length: {}, fallthrough: {}, xrefs to: {}, xrefs from: {}}}",
            match self.get_insn_length() {
                Ok(v) => format!("0x{:x}", v),
                Err(Error::LongInstruction) => ">=0xF".to_string(),
                Err(Error::NotAnInstruction) => "not an instruction".to_string(),
            },
            self.does_fallthrough(),
            self.has_xrefs_to(),
            self.has_xrefs_from()
        )
    }
}
<commit_msg>flowmeta: add default trait<commit_after>use std::fmt;

use failure::{Fail};

#[derive(Debug, Fail)]
pub enum Error {
    #[fail(display = "Not an instruction")]
    NotAnInstruction,
    #[fail(display = "Instruction too long")]
    LongInstruction,
}

/// FlowMeta is metadata that describes code flow for an instruction.
///
/// The metadata is packed into a single byte, with the expectation that,
///  there may be one `FlowMeta` instance for each (code) offset in a program.
/// However, the you should not be worried about the representation, just the interface.
#[derive(Debug, Clone, Copy, Default)]
pub struct FlowMeta(u8);

impl FlowMeta {
    // layout:
    //
    //     +-+-+-+-+-+-+-+-+
    //     |A|B|C|D|   E   |
    //     +-+-+-+-+-+-+-+-+
    //
    //  A - has xrefs to
    //  B - has xrefs from
    //  C - does other fallthrough to
    //  D - does fallthrough
    //  E - insn size

    pub fn zero() -> FlowMeta {
        FlowMeta(0x0)
    }

    /// Fetch the cached length of the instruction.
    ///
    /// If longer than 14 bytes, then will return `Error::LongInstruction` and you'll have to decode
    ///  the instruction yourself.
    ///
    /// Errors:
    ///   - NotAnInstruction
    ///   - LongInstruction
    pub fn get_insn_length(self) -> Result<u8, Error> {
        let v = self.0 & 0b0000_1111;
        match v {
            0x00 => Err(Error::NotAnInstruction),
            0x0F => Err(Error::LongInstruction),
            v => Ok(v),
        }
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    ///
    /// m.set_insn_length(0);
    /// assert_eq!(m.is_insn(), false);
    /// assert!(matches!(m.get_insn_length().err().unwrap(), Error::NotAnInstruction));
    ///
    /// m.set_insn_length(1);
    /// assert_eq!(m.is_insn(), true);
    /// assert_eq!(m.get_insn_length().unwrap(), 0x1);
    ///
    /// m.set_insn_length(2);
    /// assert_eq!(m.get_insn_length().unwrap(), 0x2);
    ///
    /// m.set_insn_length(0xE);
    /// assert_eq!(m.get_insn_length().unwrap(), 0xE);
    ///
    /// m.set_insn_length(0xF);
    /// assert_eq!(m.is_insn(), true);
    /// assert!(matches!(m.get_insn_length().err().unwrap(), Error::LongInstruction));
    /// ```
    pub fn set_insn_length(&mut self, length: u8) {
        let len = if length >= 0xF {
            // instrunction length too long
            0x0F
        } else if length == 0x0 {
            // not an instruction
            0x00
        } else {
            // good flow
            length & 0x0F
        };

        self.0 = (self.0 & 0b1111_0000) | len;
    }

    pub fn is_insn(self) -> bool {
        self.0 & 0b0000_1111 != 0
    }

    /// Does the instruction fallthrough?
    pub fn does_fallthrough(self) -> bool {
        self.0 & 0b0001_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.does_fallthrough(), false);
    ///
    /// m.set_fallthrough();
    /// assert_eq!(m.does_fallthrough(), true);
    /// ```
    pub fn set_fallthrough(&mut self) {
        self.0 |= 0b0001_0000;
    }

    /// Does another instruction fallthrough to this instruction?
    pub fn does_other_fallthrough_to(self) -> bool {
        self.0 & 0b0010_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.does_other_fallthrough_to(), false);
    ///
    /// m.set_other_fallthrough_to();
    /// assert_eq!(m.does_other_fallthrough_to(), true);
    /// ```
    pub fn set_other_fallthrough_to(&mut self) {
        self.0 |= 0b0010_0000;
    }

    /// Does the instruction have flow xrefs from it?
    /// This does not include the fallthrough flow.
    pub fn has_xrefs_from(self) -> bool {
        self.0 & 0b0100_0000 > 0
    }

    /// ```
    /// use matches::matches;
    /// use lancelot::flowmeta::*;
    ///
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_from(), false);
    ///
    /// m.set_xrefs_from();
    /// assert_eq!(m.has_xrefs_from(), true);
    /// ```
    pub fn set_xrefs_from(&mut self) {
        self.0 |= 0b0100_0000;
    }

    /// Does the instruction have flow xrefs to it?
    pub fn has_xrefs_to(self) -> bool {
        self.0 & 0b1000_0000 > 0
    }

    /// Set the bit indicating that there are flow xrefs to this instruction.
    ///
    /// ```
    /// use lancelot::flowmeta::*;
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_to(), false);
    ///
    /// m.set_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), true);
    /// ```
    pub fn set_xrefs_to(&mut self) {
        self.0 |= 0b1000_0000;
    }

    /// Unset the bit indicating that there are flow xrefs to this instruction.
    ///
    /// ```
    /// use lancelot::flowmeta::*;
    /// let mut m = FlowMeta::zero();
    /// assert_eq!(m.has_xrefs_to(), false);
    ///
    /// m.set_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), true);
    ///
    /// m.unset_xrefs_to();
    /// assert_eq!(m.has_xrefs_to(), false);
    /// ```
    pub fn unset_xrefs_to(&mut self) {
        self.0 &= 0b0111_1111
    }
}

impl fmt::Display for FlowMeta {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FlowMeta{{length: {}, fallthrough: {}, xrefs to: {}, xrefs from: {}}}",
            match self.get_insn_length() {
                Ok(v) => format!("0x{:x}", v),
                Err(Error::LongInstruction) => ">=0xF".to_string(),
                Err(Error::NotAnInstruction) => "not an instruction".to_string(),
            },
            self.does_fallthrough(),
            self.has_xrefs_to(),
            self.has_xrefs_from()
        )
    }
}
<|endoftext|>


<commit_before>//! Parse parts of an ISO 8601-formatted value.

use crate::error;
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::Iso8601;
use crate::parsing::combinator::rfc::iso8601::{
    day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
};
use crate::parsing::combinator::{ascii_char, sign};
use crate::parsing::{Parsed, ParsedItem};

impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
    // Basic: [year][month][day]
    // Extended: [year]["-"][month]["-"][day]
    // Basic: [year][dayo]
    // Extended: [year]["-"][dayo]
    // Basic: [year]["W"][week][dayk]
    // Extended: [year]["-"]["W"][week]["-"][dayk]
    /// Parse a date in the basic or extended format. Reduced precision is permitted.
    pub(crate) fn parse_date<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |input| {
            // Same for any acceptable format.
            let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?;
            *extended_kind = match ascii_char::<b'-'>(input) {
                Some(ParsedItem(new_input, ())) => {
                    input = new_input;
                    ExtendedKind::Extended
                }
                None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
            };

            let mut ret_error = match (|| {
                let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
                if extended_kind.is_extended() {
                    input = ascii_char::<b'-'>(input)
                        .ok_or(InvalidLiteral)?
                        .into_inner();
                }
                let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
                Ok(ParsedItem(input, (month, day)))
            })() {
                Ok(ParsedItem(input, (month, day))) => {
                    *parsed = parsed
                        .with_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_month(month)
                        .ok_or(InvalidComponent("month"))?
                        .with_day(day)
                        .ok_or(InvalidComponent("day"))?;
                    return Ok(input);
                }
                Err(err) => err,
            };

            match dayo(input) {
                Some(ParsedItem(input, ordinal)) => {
                    *parsed = parsed
                        .with_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_ordinal(ordinal)
                        .ok_or(InvalidComponent("ordinal"))?;
                    return Ok(input);
                }
                None => {} // The error from year-month-day will always take priority.
            }

            match (|| {
                let input = ascii_char::<b'W'>(input)
                    .ok_or((false, InvalidLiteral))?
                    .into_inner();
                let ParsedItem(mut input, week) =
                    week(input).ok_or((true, InvalidComponent("week")))?;
                if extended_kind.is_extended() {
                    input = ascii_char::<b'-'>(input)
                        .ok_or((true, InvalidLiteral))?
                        .into_inner();
                }
                let ParsedItem(input, weekday) =
                    dayk(input).ok_or((true, InvalidComponent("weekday")))?;
                Ok(ParsedItem(input, (week, weekday)))
            })() {
                Ok(ParsedItem(input, (week, weekday))) => {
                    *parsed = parsed
                        .with_iso_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_iso_week_number(week)
                        .ok_or(InvalidComponent("week"))?
                        .with_weekday(weekday)
                        .ok_or(InvalidComponent("weekday"))?;
                    return Ok(input);
                }
                Err((false, _err)) => {}
                // This error is more accurate than the one from year-month-day.
                Err((true, err)) => ret_error = err,
            }

            Err(ret_error.into())
        }
    }

    // Basic: ["T"][hour][min][sec]
    // Extended: ["T"][hour][":"][min][":"][sec]
    // Reduced precision: components after [hour] (including their preceding separator) can be
    // omitted. ["T"] can be omitted if there is no date present.
    /// Parse a time in the basic or extended format. Reduced precision is permitted.
    pub(crate) fn parse_time<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
        date_is_present: bool,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |mut input| {
            if date_is_present {
                input = ascii_char::<b'T'>(input)
                    .ok_or(InvalidLiteral)?
                    .into_inner();
            }

            let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
            match hour {
                (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
                (hour, Some(fractional_part)) => {
                    *parsed = parsed
                        .with_hour_24(hour)
                        .ok_or(InvalidComponent("hour"))?
                        .with_minute((fractional_part * 60.0) as _)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second((fractional_part * 3600.0 % 60.) as _)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(
                            (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _,
                        )
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
            };

            if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
                extended_kind
                    .coerce_extended()
                    .ok_or(InvalidComponent("minute"))?;
                input = new_input;
            };

            let mut input = match float(input) {
                Some(ParsedItem(input, (minute, None))) => {
                    extended_kind.coerce_basic();
                    parsed
                        .set_minute(minute)
                        .ok_or(InvalidComponent("minute"))?;
                    input
                }
                Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
                    // `None` is valid behavior, so don't error if this fails.
                    extended_kind.coerce_basic();
                    *parsed = parsed
                        .with_minute(minute)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second((fractional_part * 60.) as _)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(
                            (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _,
                        )
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
                // colon was present, so minutes are required
                None if extended_kind.is_extended() => {
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
                        "minute",
                    )));
                }
                None => {
                    // Missing components are assumed to be zero.
                    *parsed = parsed
                        .with_minute(0)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second(0)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(0)
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
            };

            if extended_kind.is_extended() {
                match ascii_char::<b':'>(input) {
                    Some(ParsedItem(new_input, ())) => input = new_input,
                    None => {
                        *parsed = parsed
                            .with_second(0)
                            .ok_or(InvalidComponent("second"))?
                            .with_subsecond(0)
                            .ok_or(InvalidComponent("subsecond"))?;
                        return Ok(input);
                    }
                }
            }

            let (input, second, subsecond) = match float(input) {
                Some(ParsedItem(input, (second, None))) => (input, second, 0),
                Some(ParsedItem(input, (second, Some(fractional_part)))) => {
                    (input, second, round(fractional_part * 1_000_000_000.) as _)
                }
                None if extended_kind.is_extended() => {
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
                        "second",
                    )));
                }
                // Missing components are assumed to be zero.
                None => (input, 0, 0),
            };
            *parsed = parsed
                .with_second(second)
                .ok_or(InvalidComponent("second"))?
                .with_subsecond(subsecond)
                .ok_or(InvalidComponent("subsecond"))?;

            Ok(input)
        }
    }

    // Basic: [±][hour][min] or ["Z"]
    // Extended: [±][hour][":"][min] or ["Z"]
    // Reduced precision: [±][hour] or ["Z"]
    /// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
    pub(crate) fn parse_offset<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |input| {
            if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
                *parsed = parsed
                    .with_offset_hour(0)
                    .ok_or(InvalidComponent("offset hour"))?
                    .with_offset_minute_signed(0)
                    .ok_or(InvalidComponent("offset minute"))?
                    .with_offset_second_signed(0)
                    .ok_or(InvalidComponent("offset second"))?;
                return Ok(input);
            }

            let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
            let mut input = hour(input)
                .and_then(|parsed_item| {
                    parsed_item.consume_value(|hour| {
                        parsed.set_offset_hour(if sign == b'-' {
                            -(hour as i8)
                        } else {
                            hour as _
                        })
                    })
                })
                .ok_or(InvalidComponent("offset hour"))?;

            if extended_kind.maybe_extended() {
                if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
                    extended_kind
                        .coerce_extended()
                        .ok_or(InvalidComponent("offset minute"))?;
                    input = new_input;
                };
            }

            let input = min(input)
                .and_then(|parsed_item| {
                    parsed_item.consume_value(|min| {
                        parsed.set_offset_minute_signed(if sign == b'-' {
                            -(min as i8)
                        } else {
                            min as _
                        })
                    })
                })
                .ok_or(InvalidComponent("offset minute"))?;
            // If `:` was present, the format has already been set to extended. As such, this call
            // will do nothing in that case. If there wasn't `:` but minutes were
            // present, we know it's the basic format. Do not use `?` on the call, as
            // returning `None` is valid behavior.
            extended_kind.coerce_basic();

            Ok(input)
        }
    }
}

/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
/// implementation for `no_std`
fn round(value: f64) -> f64 {
    #[cfg(feature = "std")]
    let rounded = value.round();
    #[cfg(not(feature = "std"))]
    let rounded = round_impl(value);
    rounded
}

#[cfg(not(feature = "std"))]
#[allow(clippy::missing_docs_in_private_items)]
// Based on num-trait for no_std feature
#[inline]
fn round_impl(value: f64) -> f64 {
    #[inline]
    fn fract(value: f64) -> f64 {
        if value == 0. { 0.0 } else { value % 1. }
    }

    let one = 1.;
    let h = 0.5;
    let f = fract(value);
    if f.is_nan() || f == 0. {
        value
    } else if value > 0. {
        if f < h { value - f } else { value - f + one }
    } else if -f < h {
        value - f
    } else {
        value - f - one
    }
}
<commit_msg>Simplify float rounding for use case<commit_after>//! Parse parts of an ISO 8601-formatted value.

use crate::error;
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::Iso8601;
use crate::parsing::combinator::rfc::iso8601::{
    day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
};
use crate::parsing::combinator::{ascii_char, sign};
use crate::parsing::{Parsed, ParsedItem};

impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
    // Basic: [year][month][day]
    // Extended: [year]["-"][month]["-"][day]
    // Basic: [year][dayo]
    // Extended: [year]["-"][dayo]
    // Basic: [year]["W"][week][dayk]
    // Extended: [year]["-"]["W"][week]["-"][dayk]
    /// Parse a date in the basic or extended format. Reduced precision is permitted.
    pub(crate) fn parse_date<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |input| {
            // Same for any acceptable format.
            let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?;
            *extended_kind = match ascii_char::<b'-'>(input) {
                Some(ParsedItem(new_input, ())) => {
                    input = new_input;
                    ExtendedKind::Extended
                }
                None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
            };

            let mut ret_error = match (|| {
                let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
                if extended_kind.is_extended() {
                    input = ascii_char::<b'-'>(input)
                        .ok_or(InvalidLiteral)?
                        .into_inner();
                }
                let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
                Ok(ParsedItem(input, (month, day)))
            })() {
                Ok(ParsedItem(input, (month, day))) => {
                    *parsed = parsed
                        .with_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_month(month)
                        .ok_or(InvalidComponent("month"))?
                        .with_day(day)
                        .ok_or(InvalidComponent("day"))?;
                    return Ok(input);
                }
                Err(err) => err,
            };

            match dayo(input) {
                Some(ParsedItem(input, ordinal)) => {
                    *parsed = parsed
                        .with_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_ordinal(ordinal)
                        .ok_or(InvalidComponent("ordinal"))?;
                    return Ok(input);
                }
                None => {} // The error from year-month-day will always take priority.
            }

            match (|| {
                let input = ascii_char::<b'W'>(input)
                    .ok_or((false, InvalidLiteral))?
                    .into_inner();
                let ParsedItem(mut input, week) =
                    week(input).ok_or((true, InvalidComponent("week")))?;
                if extended_kind.is_extended() {
                    input = ascii_char::<b'-'>(input)
                        .ok_or((true, InvalidLiteral))?
                        .into_inner();
                }
                let ParsedItem(input, weekday) =
                    dayk(input).ok_or((true, InvalidComponent("weekday")))?;
                Ok(ParsedItem(input, (week, weekday)))
            })() {
                Ok(ParsedItem(input, (week, weekday))) => {
                    *parsed = parsed
                        .with_iso_year(year)
                        .ok_or(InvalidComponent("year"))?
                        .with_iso_week_number(week)
                        .ok_or(InvalidComponent("week"))?
                        .with_weekday(weekday)
                        .ok_or(InvalidComponent("weekday"))?;
                    return Ok(input);
                }
                Err((false, _err)) => {}
                // This error is more accurate than the one from year-month-day.
                Err((true, err)) => ret_error = err,
            }

            Err(ret_error.into())
        }
    }

    // Basic: ["T"][hour][min][sec]
    // Extended: ["T"][hour][":"][min][":"][sec]
    // Reduced precision: components after [hour] (including their preceding separator) can be
    // omitted. ["T"] can be omitted if there is no date present.
    /// Parse a time in the basic or extended format. Reduced precision is permitted.
    pub(crate) fn parse_time<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
        date_is_present: bool,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |mut input| {
            if date_is_present {
                input = ascii_char::<b'T'>(input)
                    .ok_or(InvalidLiteral)?
                    .into_inner();
            }

            let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
            match hour {
                (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
                (hour, Some(fractional_part)) => {
                    *parsed = parsed
                        .with_hour_24(hour)
                        .ok_or(InvalidComponent("hour"))?
                        .with_minute((fractional_part * 60.0) as _)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second((fractional_part * 3600.0 % 60.) as _)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(
                            (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _,
                        )
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
            };

            if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
                extended_kind
                    .coerce_extended()
                    .ok_or(InvalidComponent("minute"))?;
                input = new_input;
            };

            let mut input = match float(input) {
                Some(ParsedItem(input, (minute, None))) => {
                    extended_kind.coerce_basic();
                    parsed
                        .set_minute(minute)
                        .ok_or(InvalidComponent("minute"))?;
                    input
                }
                Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
                    // `None` is valid behavior, so don't error if this fails.
                    extended_kind.coerce_basic();
                    *parsed = parsed
                        .with_minute(minute)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second((fractional_part * 60.) as _)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(
                            (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _,
                        )
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
                // colon was present, so minutes are required
                None if extended_kind.is_extended() => {
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
                        "minute",
                    )));
                }
                None => {
                    // Missing components are assumed to be zero.
                    *parsed = parsed
                        .with_minute(0)
                        .ok_or(InvalidComponent("minute"))?
                        .with_second(0)
                        .ok_or(InvalidComponent("second"))?
                        .with_subsecond(0)
                        .ok_or(InvalidComponent("subsecond"))?;
                    return Ok(input);
                }
            };

            if extended_kind.is_extended() {
                match ascii_char::<b':'>(input) {
                    Some(ParsedItem(new_input, ())) => input = new_input,
                    None => {
                        *parsed = parsed
                            .with_second(0)
                            .ok_or(InvalidComponent("second"))?
                            .with_subsecond(0)
                            .ok_or(InvalidComponent("subsecond"))?;
                        return Ok(input);
                    }
                }
            }

            let (input, second, subsecond) = match float(input) {
                Some(ParsedItem(input, (second, None))) => (input, second, 0),
                Some(ParsedItem(input, (second, Some(fractional_part)))) => {
                    (input, second, round(fractional_part * 1_000_000_000.) as _)
                }
                None if extended_kind.is_extended() => {
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
                        "second",
                    )));
                }
                // Missing components are assumed to be zero.
                None => (input, 0, 0),
            };
            *parsed = parsed
                .with_second(second)
                .ok_or(InvalidComponent("second"))?
                .with_subsecond(subsecond)
                .ok_or(InvalidComponent("subsecond"))?;

            Ok(input)
        }
    }

    // Basic: [±][hour][min] or ["Z"]
    // Extended: [±][hour][":"][min] or ["Z"]
    // Reduced precision: [±][hour] or ["Z"]
    /// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
    pub(crate) fn parse_offset<'a>(
        parsed: &'a mut Parsed,
        extended_kind: &'a mut ExtendedKind,
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
        move |input| {
            if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
                *parsed = parsed
                    .with_offset_hour(0)
                    .ok_or(InvalidComponent("offset hour"))?
                    .with_offset_minute_signed(0)
                    .ok_or(InvalidComponent("offset minute"))?
                    .with_offset_second_signed(0)
                    .ok_or(InvalidComponent("offset second"))?;
                return Ok(input);
            }

            let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
            let mut input = hour(input)
                .and_then(|parsed_item| {
                    parsed_item.consume_value(|hour| {
                        parsed.set_offset_hour(if sign == b'-' {
                            -(hour as i8)
                        } else {
                            hour as _
                        })
                    })
                })
                .ok_or(InvalidComponent("offset hour"))?;

            if extended_kind.maybe_extended() {
                if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
                    extended_kind
                        .coerce_extended()
                        .ok_or(InvalidComponent("offset minute"))?;
                    input = new_input;
                };
            }

            let input = min(input)
                .and_then(|parsed_item| {
                    parsed_item.consume_value(|min| {
                        parsed.set_offset_minute_signed(if sign == b'-' {
                            -(min as i8)
                        } else {
                            min as _
                        })
                    })
                })
                .ok_or(InvalidComponent("offset minute"))?;
            // If `:` was present, the format has already been set to extended. As such, this call
            // will do nothing in that case. If there wasn't `:` but minutes were
            // present, we know it's the basic format. Do not use `?` on the call, as
            // returning `None` is valid behavior.
            extended_kind.coerce_basic();

            Ok(input)
        }
    }
}

/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
/// implementation for `no_std`
fn round(value: f64) -> f64 {
    #[cfg(feature = "std")]
    {
        value.round()
    }
    #[cfg(not(feature = "std"))]
    {
        round_impl(value)
    }
}

#[cfg(not(feature = "std"))]
#[allow(clippy::missing_docs_in_private_items)]
fn round_impl(value: f64) -> f64 {
    debug_assert!(value.is_sign_positive() && !value.is_nan());

    let f = value % 1.;
    if f < 0.5 { value - f } else { value - f + 1. }
}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use std::{sync::Arc, time::Duration};

use aptos_config::config::{LedgerPrunerConfig, StateMerklePrunerConfig};
use proptest::prelude::*;

use crate::{
    error_if_version_is_pruned, get_first_seq_num_and_limit,
    pruner::{
        ledger_pruner_manager::LedgerPrunerManager, state_pruner_manager::StatePrunerManager,
    },
    test_helper,
    test_helper::{arb_blocks_to_commit, put_as_state_root, put_transaction_info},
    AptosDB, PrunerManager, ROCKSDB_PROPERTIES,
};

use aptos_crypto::{hash::CryptoHash, HashValue};
use aptos_state_view::state_storage_usage::StateStorageUsage;
use aptos_temppath::TempPath;
use aptos_types::{
    proof::SparseMerkleLeafNode,
    state_store::{state_key::StateKey, state_value::StateValue},
    transaction::{ExecutionStatus, TransactionInfo},
};
use storage_interface::{DbReader, ExecutedTrees, Order};
use test_helper::{test_save_blocks_impl, test_sync_transactions_impl};

proptest! {
    #![proptest_config(ProptestConfig::with_cases(10))]

    #[test]
    fn test_save_blocks(input in arb_blocks_to_commit(), threshold in 10..20usize) {
        test_save_blocks_impl(input, threshold);
    }

    #[test]
    fn test_sync_transactions(input in arb_blocks_to_commit(), threshold in 10..20usize) {
        test_sync_transactions_impl(input, threshold);
    }
}

#[test]
fn test_get_first_seq_num_and_limit() {
    assert!(get_first_seq_num_and_limit(Order::Ascending, 0, 0).is_err());

    // ascending
    assert_eq!(
        get_first_seq_num_and_limit(Order::Ascending, 0, 4).unwrap(),
        (0, 4)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Ascending, 0, 1).unwrap(),
        (0, 1)
    );

    // descending
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 1).unwrap(),
        (2, 1)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 2).unwrap(),
        (1, 2)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 3).unwrap(),
        (0, 3)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 4).unwrap(),
        (0, 3)
    );
}

#[test]
fn test_too_many_requested() {
    let tmp_dir = TempPath::new();
    let db = AptosDB::new_for_test(&tmp_dir);

    assert!(db.get_transactions(0, 1001 /* limit */, 0, true).is_err());
    assert!(db.get_transaction_outputs(0, 1001 /* limit */, 0).is_err());
}

#[test]
fn test_storage_config() {
    let tmp_dir = TempPath::new();
    let aptos_db = AptosDB::new_for_test(&tmp_dir);
    for enable_ledger in [false, true] {
        for enable_state in [false, true] {
            let state_pruner = StatePrunerManager::new(
                Arc::clone(&aptos_db.state_merkle_db),
                StateMerklePrunerConfig {
                    enable: enable_state,
                    prune_window: 20,
                    batch_size: 1,
                    user_pruning_window_offset: 0,
                },
            );

            assert_eq!(state_pruner.is_pruner_enabled(), enable_state);
            assert_eq!(state_pruner.get_pruner_window(), 20);

            let ledger_pruner = LedgerPrunerManager::new(
                Arc::clone(&aptos_db.ledger_db),
                Arc::clone(&aptos_db.state_store),
                LedgerPrunerConfig {
                    enable: enable_ledger,
                    prune_window: 100,
                    batch_size: 1,
                    user_pruning_window_offset: 0,
                },
            );
            assert_eq!(ledger_pruner.is_pruner_enabled(), enable_ledger);
            assert_eq!(ledger_pruner.get_pruner_window(), 100);
        }
    }
}

#[test]
fn test_error_if_version_is_pruned() {
    let tmp_dir = TempPath::new();
    let aptos_db = AptosDB::new_for_test(&tmp_dir);
    let state_pruner = StatePrunerManager::new(
        Arc::clone(&aptos_db.state_merkle_db),
        StateMerklePrunerConfig {
            enable: true,
            prune_window: 0,
            batch_size: 1,
            user_pruning_window_offset: 0,
        },
    );

    let ledger_pruner = LedgerPrunerManager::new(
        Arc::clone(&aptos_db.ledger_db),
        Arc::clone(&aptos_db.state_store),
        LedgerPrunerConfig {
            enable: true,
            prune_window: 0,
            batch_size: 1,
            user_pruning_window_offset: 0,
        },
    );
    state_pruner.testonly_update_min_version(5);
    ledger_pruner.testonly_update_min_version(10);
    assert_eq!(
        error_if_version_is_pruned(&state_pruner, "State", 4)
            .unwrap_err()
            .to_string(),
        "State version 4 is pruned, min available version is 5."
    );
    assert!(error_if_version_is_pruned(&state_pruner, "State", 5).is_ok());
    assert_eq!(
        error_if_version_is_pruned(&ledger_pruner, "Transaction", 9)
            .unwrap_err()
            .to_string(),
        "Transaction version 9 is pruned, min available version is 10."
    );
    assert!(error_if_version_is_pruned(&ledger_pruner, "Transaction", 10).is_ok());
}

#[test]
fn test_get_latest_executed_trees() {
    let tmp_dir = TempPath::new();
    let db = AptosDB::new_for_test(&tmp_dir);

    // entirely emtpy db
    let empty = db.get_latest_executed_trees().unwrap();
    assert!(empty.is_same_view(&ExecutedTrees::new_empty()));

    // bootstrapped db (any transaction info is in)
    let key = StateKey::Raw(String::from("test_key").into_bytes());
    let value = StateValue::from(String::from("test_val").into_bytes());
    let hash = SparseMerkleLeafNode::new(key.hash(), value.hash()).hash();
    put_as_state_root(&db, 0, key, value);
    let txn_info = TransactionInfo::new(
        HashValue::random(),
        HashValue::random(),
        HashValue::random(),
        Some(hash),
        0,
        ExecutionStatus::MiscellaneousError(None),
    );
    put_transaction_info(&db, 0, &txn_info);

    let bootstrapped = db.get_latest_executed_trees().unwrap();
    assert!(
        bootstrapped.is_same_view(&ExecutedTrees::new_at_state_checkpoint(
            txn_info.state_checkpoint_hash().unwrap(),
            StateStorageUsage::new_untracked(),
            vec![txn_info.hash()],
            1,
        ))
    );
}

#[test]
fn test_rocksdb_properties_reporter() {
    fn get_metric() -> i64 {
        ROCKSDB_PROPERTIES
            .get_metric_with_label_values(&[
                "transaction_info",
                "aptos_rocksdb_is-file-deletions-enabled",
            ])
            .unwrap()
            .get()
    }

    assert_eq!(get_metric(), 0);
    let tmp_dir = TempPath::new();
    let _db = AptosDB::new_for_test(&tmp_dir);
    std::thread::sleep(Duration::from_secs(1));
    assert_eq!(get_metric(), 1);
}
<commit_msg>[storage] trivial: test_pruner_config simpler<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use std::{sync::Arc, time::Duration};

use aptos_config::config::{LedgerPrunerConfig, StateMerklePrunerConfig};
use proptest::prelude::*;

use crate::{
    error_if_version_is_pruned, get_first_seq_num_and_limit,
    pruner::{
        ledger_pruner_manager::LedgerPrunerManager, state_pruner_manager::StatePrunerManager,
    },
    test_helper,
    test_helper::{arb_blocks_to_commit, put_as_state_root, put_transaction_info},
    AptosDB, PrunerManager, ROCKSDB_PROPERTIES,
};

use aptos_crypto::{hash::CryptoHash, HashValue};
use aptos_state_view::state_storage_usage::StateStorageUsage;
use aptos_temppath::TempPath;
use aptos_types::{
    proof::SparseMerkleLeafNode,
    state_store::{state_key::StateKey, state_value::StateValue},
    transaction::{ExecutionStatus, TransactionInfo},
};
use storage_interface::{DbReader, ExecutedTrees, Order};
use test_helper::{test_save_blocks_impl, test_sync_transactions_impl};

proptest! {
    #![proptest_config(ProptestConfig::with_cases(10))]

    #[test]
    fn test_save_blocks(input in arb_blocks_to_commit(), threshold in 10..20usize) {
        test_save_blocks_impl(input, threshold);
    }

    #[test]
    fn test_sync_transactions(input in arb_blocks_to_commit(), threshold in 10..20usize) {
        test_sync_transactions_impl(input, threshold);
    }
}

#[test]
fn test_get_first_seq_num_and_limit() {
    assert!(get_first_seq_num_and_limit(Order::Ascending, 0, 0).is_err());

    // ascending
    assert_eq!(
        get_first_seq_num_and_limit(Order::Ascending, 0, 4).unwrap(),
        (0, 4)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Ascending, 0, 1).unwrap(),
        (0, 1)
    );

    // descending
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 1).unwrap(),
        (2, 1)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 2).unwrap(),
        (1, 2)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 3).unwrap(),
        (0, 3)
    );
    assert_eq!(
        get_first_seq_num_and_limit(Order::Descending, 2, 4).unwrap(),
        (0, 3)
    );
}

#[test]
fn test_too_many_requested() {
    let tmp_dir = TempPath::new();
    let db = AptosDB::new_for_test(&tmp_dir);

    assert!(db.get_transactions(0, 1001 /* limit */, 0, true).is_err());
    assert!(db.get_transaction_outputs(0, 1001 /* limit */, 0).is_err());
}

#[test]
fn test_pruner_config() {
    let tmp_dir = TempPath::new();
    let aptos_db = AptosDB::new_for_test(&tmp_dir);
    for enable in [false, true] {
        let state_pruner = StatePrunerManager::new(
            Arc::clone(&aptos_db.state_merkle_db),
            StateMerklePrunerConfig {
                enable,
                prune_window: 20,
                batch_size: 1,
                user_pruning_window_offset: 0,
            },
        );
        assert_eq!(state_pruner.is_pruner_enabled(), enable);
        assert_eq!(state_pruner.get_pruner_window(), 20);

        let ledger_pruner = LedgerPrunerManager::new(
            Arc::clone(&aptos_db.ledger_db),
            Arc::clone(&aptos_db.state_store),
            LedgerPrunerConfig {
                enable,
                prune_window: 100,
                batch_size: 1,
                user_pruning_window_offset: 0,
            },
        );
        assert_eq!(ledger_pruner.is_pruner_enabled(), enable);
        assert_eq!(ledger_pruner.get_pruner_window(), 100);
    }
}

#[test]
fn test_error_if_version_is_pruned() {
    let tmp_dir = TempPath::new();
    let aptos_db = AptosDB::new_for_test(&tmp_dir);
    let state_pruner = StatePrunerManager::new(
        Arc::clone(&aptos_db.state_merkle_db),
        StateMerklePrunerConfig {
            enable: true,
            prune_window: 0,
            batch_size: 1,
            user_pruning_window_offset: 0,
        },
    );

    let ledger_pruner = LedgerPrunerManager::new(
        Arc::clone(&aptos_db.ledger_db),
        Arc::clone(&aptos_db.state_store),
        LedgerPrunerConfig {
            enable: true,
            prune_window: 0,
            batch_size: 1,
            user_pruning_window_offset: 0,
        },
    );
    state_pruner.testonly_update_min_version(5);
    ledger_pruner.testonly_update_min_version(10);
    assert_eq!(
        error_if_version_is_pruned(&state_pruner, "State", 4)
            .unwrap_err()
            .to_string(),
        "State version 4 is pruned, min available version is 5."
    );
    assert!(error_if_version_is_pruned(&state_pruner, "State", 5).is_ok());
    assert_eq!(
        error_if_version_is_pruned(&ledger_pruner, "Transaction", 9)
            .unwrap_err()
            .to_string(),
        "Transaction version 9 is pruned, min available version is 10."
    );
    assert!(error_if_version_is_pruned(&ledger_pruner, "Transaction", 10).is_ok());
}

#[test]
fn test_get_latest_executed_trees() {
    let tmp_dir = TempPath::new();
    let db = AptosDB::new_for_test(&tmp_dir);

    // entirely emtpy db
    let empty = db.get_latest_executed_trees().unwrap();
    assert!(empty.is_same_view(&ExecutedTrees::new_empty()));

    // bootstrapped db (any transaction info is in)
    let key = StateKey::Raw(String::from("test_key").into_bytes());
    let value = StateValue::from(String::from("test_val").into_bytes());
    let hash = SparseMerkleLeafNode::new(key.hash(), value.hash()).hash();
    put_as_state_root(&db, 0, key, value);
    let txn_info = TransactionInfo::new(
        HashValue::random(),
        HashValue::random(),
        HashValue::random(),
        Some(hash),
        0,
        ExecutionStatus::MiscellaneousError(None),
    );
    put_transaction_info(&db, 0, &txn_info);

    let bootstrapped = db.get_latest_executed_trees().unwrap();
    assert!(
        bootstrapped.is_same_view(&ExecutedTrees::new_at_state_checkpoint(
            txn_info.state_checkpoint_hash().unwrap(),
            StateStorageUsage::new_untracked(),
            vec![txn_info.hash()],
            1,
        ))
    );
}

#[test]
fn test_rocksdb_properties_reporter() {
    fn get_metric() -> i64 {
        ROCKSDB_PROPERTIES
            .get_metric_with_label_values(&[
                "transaction_info",
                "aptos_rocksdb_is-file-deletions-enabled",
            ])
            .unwrap()
            .get()
    }

    assert_eq!(get_metric(), 0);
    let tmp_dir = TempPath::new();
    let _db = AptosDB::new_for_test(&tmp_dir);
    std::thread::sleep(Duration::from_secs(1));
    assert_eq!(get_metric(), 1);
}
<|endoftext|>


<commit_before>use satyrs::cnf::{CNF, Assignment, PartialAssignment};

/**
 * DPLL Algorithm implementation
 * Start by cloning the cnf so we can modify the HashMap
 * Clone might be slow, consider a faster option
 * TODO: Consider using HashSet instead of Vec for the clauses
 */
#[allow(non_snake_case)]
pub fn DPLL(cnf: &CNF) -> Option<Assignment> {
    let _cnf = cnf.clone();
    println!("DPLL {:?}", _cnf);
    let p_assn = PartialAssignment::new(_cnf.nvar as usize);
    match _dpll(&_cnf, p_assn){
        Some(assn) => Some(assn.assignment.iter().map(|a| a.unwrap()).collect()),
        None => None
    }
}

#[allow(unused_variables)]
fn _dpll(cnf: &CNF, mut p_assn: PartialAssignment) -> Option<PartialAssignment> {
    // If consistent set of literals, return True
    if cnf.clauses.is_empty() {
        return Some(p_assn)  // Display optional value
    }
    for unit in &cnf.units {
		println!("IM JUST A LONELY UNIT");
    }
    // If contains an empty clause return False
    // For every unit-clause, unit-propogate
    // For ever pure literal, pure literal assign
    // Choose literal L for split
    // Return DPLL with L and -L

    // TODO: Need to implement. Just testing with a PartialAssignment
    println!("PARTIAL ASSIGNMENT:\n{}", p_assn);
    for x in 0..(cnf.nvar) {
        p_assn.assign(x as usize, true);
    }
    Some(p_assn)
}
<commit_msg>LONELY UNIT<commit_after>use satyrs::cnf::{CNF, Assignment, PartialAssignment};

/**
 * DPLL Algorithm implementation
 * Start by cloning the cnf so we can modify the HashMap
 * Clone might be slow, consider a faster option
 * TODO: Consider using HashSet instead of Vec for the clauses
 */
#[allow(non_snake_case)]
pub fn DPLL(cnf: &CNF) -> Option<Assignment> {
    let _cnf = cnf.clone();
    println!("DPLL {:?}", _cnf);
    let p_assn = PartialAssignment::new(_cnf.nvar as usize);
    match _dpll(&_cnf, p_assn){
        Some(assn) => Some(assn.assignment.iter().map(|a| a.unwrap()).collect()),
        None => None
    }
}

#[allow(unused_variables)]
fn _dpll(cnf: &CNF, mut p_assn: PartialAssignment) -> Option<PartialAssignment> {
    // If consistent set of literals, return True
    if cnf.clauses.is_empty() {
        return Some(p_assn)  // Display optional value
    }
    for unit in &cnf.units {

		println!("{}: IM JUST A LONELY UNIT", unit);
    }
    // If contains an empty clause return False
    // For every unit-clause, unit-propogate
    // For ever pure literal, pure literal assign
    // Choose literal L for split
    // Return DPLL with L and -L

    // TODO: Need to implement. Just testing with a PartialAssignment
    println!("PARTIAL ASSIGNMENT:\n{}", p_assn);
    for x in 0..(cnf.nvar) {
        p_assn.assign(x as usize, true);
    }
    Some(p_assn)
}
<|endoftext|>


<commit_before>use rustc::ty::{self, Ty, Instance};
use rustc::ty::layout::{Size, Align, LayoutOf};
use rustc::mir::interpret::{Scalar, Pointer, InterpResult, PointerArithmetic,};

use super::{InterpCx, Machine, MemoryKind, FnVal};

impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
    /// Creates a dynamic vtable for the given type and vtable origin. This is used only for
    /// objects.
    ///
    /// The `trait_ref` encodes the erased self type. Hence if we are
    /// making an object `Foo<Trait>` from a value of type `Foo<T>`, then
    /// `trait_ref` would map `T:Trait`.
    pub fn get_vtable(
        &mut self,
        ty: Ty<'tcx>,
        poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
    ) -> InterpResult<'tcx, Pointer<M::PointerTag>> {
        trace!("get_vtable(trait_ref={:?})", poly_trait_ref);

        let (ty, poly_trait_ref) = self.tcx.erase_regions(&(ty, poly_trait_ref));

        if let Some(&vtable) = self.vtables.get(&(ty, poly_trait_ref)) {
            // This means we guarantee that there are no duplicate vtables, we will
            // always use the same vtable for the same (Type, Trait) combination.
            // That's not what happens in rustc, but emulating per-crate deduplication
            // does not sound like it actually makes anything any better.
            return Ok(vtable);
        }

        let methods = if let Some(poly_trait_ref) = poly_trait_ref {
            let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty);
            let trait_ref = self.tcx.erase_regions(&trait_ref);

            self.tcx.vtable_methods(trait_ref)
        } else {
            &[]
        };

        let layout = self.layout_of(ty)?;
        assert!(!layout.is_unsized(), "can't create a vtable for an unsized type");
        let size = layout.size.bytes();
        let align = layout.align.abi.bytes();

        let ptr_size = self.pointer_size();
        let ptr_align = self.tcx.data_layout.pointer_align.abi;
        // /////////////////////////////////////////////////////////////////////////////////////////
        // If you touch this code, be sure to also make the corresponding changes to
        // `get_vtable` in rust_codegen_llvm/meth.rs
        // /////////////////////////////////////////////////////////////////////////////////////////
        let vtable = self.memory.allocate(
            ptr_size * (3 + methods.len() as u64),
            ptr_align,
            MemoryKind::Vtable,
        );
        let tcx = &*self.tcx;

        let drop = Instance::resolve_drop_in_place(*tcx, ty);
        let drop = self.memory.create_fn_alloc(FnVal::Instance(drop));

        // no need to do any alignment checks on the memory accesses below, because we know the
        // allocation is correctly aligned as we created it above. Also we're only offsetting by
        // multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`.
        self.memory
            .get_mut(vtable.alloc_id)?
            .write_ptr_sized(tcx, vtable, Scalar::Ptr(drop).into())?;

        let size_ptr = vtable.offset(ptr_size, self)?;
        self.memory
            .get_mut(size_ptr.alloc_id)?
            .write_ptr_sized(tcx, size_ptr, Scalar::from_uint(size, ptr_size).into())?;
        let align_ptr = vtable.offset(ptr_size * 2, self)?;
        self.memory
            .get_mut(align_ptr.alloc_id)?
            .write_ptr_sized(tcx, align_ptr, Scalar::from_uint(align, ptr_size).into())?;

        for (i, method) in methods.iter().enumerate() {
            if let Some((def_id, substs)) = *method {
                // resolve for vtable: insert shims where needed
                let instance = ty::Instance::resolve_for_vtable(
                    *self.tcx,
                    self.param_env,
                    def_id,
                    substs,
                ).ok_or_else(|| err_inval!(TooGeneric))?;
                let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance));
                let method_ptr = vtable.offset(ptr_size * (3 + i as u64), self)?;
                self.memory
                    .get_mut(method_ptr.alloc_id)?
                    .write_ptr_sized(tcx, method_ptr, Scalar::Ptr(fn_ptr).into())?;
            }
        }

        self.memory.mark_immutable(vtable.alloc_id)?;
        assert!(self.vtables.insert((ty, poly_trait_ref), vtable).is_none());

        Ok(vtable)
    }

    /// Returns the drop fn instance as well as the actual dynamic type
    pub fn read_drop_type_from_vtable(
        &self,
        vtable: Scalar<M::PointerTag>,
    ) -> InterpResult<'tcx, (ty::Instance<'tcx>, Ty<'tcx>)> {
        // we don't care about the pointee type, we just want a pointer
        let vtable = self.memory.check_ptr_access(
            vtable,
            self.tcx.data_layout.pointer_size,
            self.tcx.data_layout.pointer_align.abi,
        )?.expect("cannot be a ZST");
        let drop_fn = self.memory
            .get(vtable.alloc_id)?
            .read_ptr_sized(self, vtable)?
            .not_undef()?;
        // We *need* an instance here, no other kind of function value, to be able
        // to determine the type.
        let drop_instance = self.memory.get_fn(drop_fn)?.as_instance()?;
        trace!("Found drop fn: {:?}", drop_instance);
        let fn_sig = drop_instance.ty(*self.tcx).fn_sig(*self.tcx);
        let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, &fn_sig);
        // The drop function takes `*mut T` where `T` is the type being dropped, so get that.
        let ty = fn_sig.inputs()[0].builtin_deref(true).unwrap().ty;
        Ok((drop_instance, ty))
    }

    pub fn read_size_and_align_from_vtable(
        &self,
        vtable: Scalar<M::PointerTag>,
    ) -> InterpResult<'tcx, (Size, Align)> {
        let pointer_size = self.pointer_size();
        // We check for size = 3*ptr_size, that covers the drop fn (unused here),
        // the size, and the align (which we read below).
        let vtable = self.memory.check_ptr_access(
            vtable,
            3*pointer_size,
            self.tcx.data_layout.pointer_align.abi,
        )?.expect("cannot be a ZST");
        let alloc = self.memory.get(vtable.alloc_id)?;
        let size = alloc.read_ptr_sized(
            self,
            vtable.offset(pointer_size, self)?
        )?.to_bits(pointer_size)? as u64;
        let align = alloc.read_ptr_sized(
            self,
            vtable.offset(pointer_size * 2, self)?,
        )?.to_bits(pointer_size)? as u64;
        Ok((Size::from_bytes(size), Align::from_bytes(align).unwrap()))
    }
}
<commit_msg>rustc_mir: disallow non-monomorphic vtables.<commit_after>use rustc::ty::{self, Ty, Instance, TypeFoldable};
use rustc::ty::layout::{Size, Align, LayoutOf};
use rustc::mir::interpret::{Scalar, Pointer, InterpResult, PointerArithmetic,};

use super::{InterpCx, Machine, MemoryKind, FnVal};

impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
    /// Creates a dynamic vtable for the given type and vtable origin. This is used only for
    /// objects.
    ///
    /// The `trait_ref` encodes the erased self type. Hence if we are
    /// making an object `Foo<Trait>` from a value of type `Foo<T>`, then
    /// `trait_ref` would map `T:Trait`.
    pub fn get_vtable(
        &mut self,
        ty: Ty<'tcx>,
        poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
    ) -> InterpResult<'tcx, Pointer<M::PointerTag>> {
        trace!("get_vtable(trait_ref={:?})", poly_trait_ref);

        let (ty, poly_trait_ref) = self.tcx.erase_regions(&(ty, poly_trait_ref));

        // All vtables must be monomorphic, bail out otherwise.
        if ty.needs_subst() || poly_trait_ref.needs_subst() {
            throw_inval!(TooGeneric);
        }

        if let Some(&vtable) = self.vtables.get(&(ty, poly_trait_ref)) {
            // This means we guarantee that there are no duplicate vtables, we will
            // always use the same vtable for the same (Type, Trait) combination.
            // That's not what happens in rustc, but emulating per-crate deduplication
            // does not sound like it actually makes anything any better.
            return Ok(vtable);
        }

        let methods = if let Some(poly_trait_ref) = poly_trait_ref {
            let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty);
            let trait_ref = self.tcx.erase_regions(&trait_ref);

            self.tcx.vtable_methods(trait_ref)
        } else {
            &[]
        };

        let layout = self.layout_of(ty)?;
        assert!(!layout.is_unsized(), "can't create a vtable for an unsized type");
        let size = layout.size.bytes();
        let align = layout.align.abi.bytes();

        let ptr_size = self.pointer_size();
        let ptr_align = self.tcx.data_layout.pointer_align.abi;
        // /////////////////////////////////////////////////////////////////////////////////////////
        // If you touch this code, be sure to also make the corresponding changes to
        // `get_vtable` in rust_codegen_llvm/meth.rs
        // /////////////////////////////////////////////////////////////////////////////////////////
        let vtable = self.memory.allocate(
            ptr_size * (3 + methods.len() as u64),
            ptr_align,
            MemoryKind::Vtable,
        );
        let tcx = &*self.tcx;

        let drop = Instance::resolve_drop_in_place(*tcx, ty);
        let drop = self.memory.create_fn_alloc(FnVal::Instance(drop));

        // no need to do any alignment checks on the memory accesses below, because we know the
        // allocation is correctly aligned as we created it above. Also we're only offsetting by
        // multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`.
        self.memory
            .get_mut(vtable.alloc_id)?
            .write_ptr_sized(tcx, vtable, Scalar::Ptr(drop).into())?;

        let size_ptr = vtable.offset(ptr_size, self)?;
        self.memory
            .get_mut(size_ptr.alloc_id)?
            .write_ptr_sized(tcx, size_ptr, Scalar::from_uint(size, ptr_size).into())?;
        let align_ptr = vtable.offset(ptr_size * 2, self)?;
        self.memory
            .get_mut(align_ptr.alloc_id)?
            .write_ptr_sized(tcx, align_ptr, Scalar::from_uint(align, ptr_size).into())?;

        for (i, method) in methods.iter().enumerate() {
            if let Some((def_id, substs)) = *method {
                // resolve for vtable: insert shims where needed
                let instance = ty::Instance::resolve_for_vtable(
                    *self.tcx,
                    self.param_env,
                    def_id,
                    substs,
                ).ok_or_else(|| err_inval!(TooGeneric))?;
                let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance));
                let method_ptr = vtable.offset(ptr_size * (3 + i as u64), self)?;
                self.memory
                    .get_mut(method_ptr.alloc_id)?
                    .write_ptr_sized(tcx, method_ptr, Scalar::Ptr(fn_ptr).into())?;
            }
        }

        self.memory.mark_immutable(vtable.alloc_id)?;
        assert!(self.vtables.insert((ty, poly_trait_ref), vtable).is_none());

        Ok(vtable)
    }

    /// Returns the drop fn instance as well as the actual dynamic type
    pub fn read_drop_type_from_vtable(
        &self,
        vtable: Scalar<M::PointerTag>,
    ) -> InterpResult<'tcx, (ty::Instance<'tcx>, Ty<'tcx>)> {
        // we don't care about the pointee type, we just want a pointer
        let vtable = self.memory.check_ptr_access(
            vtable,
            self.tcx.data_layout.pointer_size,
            self.tcx.data_layout.pointer_align.abi,
        )?.expect("cannot be a ZST");
        let drop_fn = self.memory
            .get(vtable.alloc_id)?
            .read_ptr_sized(self, vtable)?
            .not_undef()?;
        // We *need* an instance here, no other kind of function value, to be able
        // to determine the type.
        let drop_instance = self.memory.get_fn(drop_fn)?.as_instance()?;
        trace!("Found drop fn: {:?}", drop_instance);
        let fn_sig = drop_instance.ty(*self.tcx).fn_sig(*self.tcx);
        let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, &fn_sig);
        // The drop function takes `*mut T` where `T` is the type being dropped, so get that.
        let ty = fn_sig.inputs()[0].builtin_deref(true).unwrap().ty;
        Ok((drop_instance, ty))
    }

    pub fn read_size_and_align_from_vtable(
        &self,
        vtable: Scalar<M::PointerTag>,
    ) -> InterpResult<'tcx, (Size, Align)> {
        let pointer_size = self.pointer_size();
        // We check for size = 3*ptr_size, that covers the drop fn (unused here),
        // the size, and the align (which we read below).
        let vtable = self.memory.check_ptr_access(
            vtable,
            3*pointer_size,
            self.tcx.data_layout.pointer_align.abi,
        )?.expect("cannot be a ZST");
        let alloc = self.memory.get(vtable.alloc_id)?;
        let size = alloc.read_ptr_sized(
            self,
            vtable.offset(pointer_size, self)?
        )?.to_bits(pointer_size)? as u64;
        let align = alloc.read_ptr_sized(
            self,
            vtable.offset(pointer_size * 2, self)?,
        )?.to_bits(pointer_size)? as u64;
        Ok((Size::from_bytes(size), Align::from_bytes(align).unwrap()))
    }
}
<|endoftext|>


<commit_before>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]",
                              item.title,
                              title,
                              item.page,
                              item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn print_reply(vec: &Vec<NodeType>,
               depth: u8,
               scrollY: usize,
               y: usize,
               rustbox: &rustbox::RustBox)
               -> usize {
    let padding = (0..depth).map(|_| "├─").collect::<Vec<_>>().join("");
    let mut m = 0;
    let mut recursive_offset = 0;
    let mut total_y = 0;
    let mut line = String::new();

    // clean up lines
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    for (j, node) in vec2.enumerate() {
        total_y = y + m + recursive_offset;
        if scrollY + 1 < total_y {
            let node2 = node.clone();
            match node2 {
                NodeType::Text(n) => {
                    if n.data != "" {
                        line = format!("{}{}", line, n.data);
                    }
                }
                NodeType::Image(n) => {
                    print_default(rustbox,
                                  0,
                                  total_y - scrollY,
                                  format!("{}[{}] = Image {}", padding, m, n.data));
                    m += 1;
                }
                NodeType::BlockQuote(n) => {
                    print_default(rustbox,
                                  0,
                                  total_y - scrollY,
                                  format!("{}[{}] = BlockQuote", padding, m));
                    m += 1;
                    recursive_offset += print_reply(&n.data,
                                                    depth + 1,
                                                    scrollY,
                                                    total_y + 1,
                                                    &rustbox);
                }
                NodeType::Br(n) => {
                    if !line.is_empty() {
                        print_default(rustbox,
                                      0,
                                      total_y - scrollY,
                                      format!(" {}{}", padding, line));
                        line = String::new();
                    }
                    m += 1;
                }
            }
        }
    }

    if !line.is_empty() {
        total_y = y + m + recursive_offset;
        print_default(rustbox,
                      0,
                      total_y - scrollY,
                      format!(" {}{}  ", padding, line));
        line = String::new();
        m += 1;
    }

    m + recursive_offset
}

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = print_reply(&reply.body, 0, scrollY, y, &rustbox);

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<commit_msg>show - print reply - append image to line<commit_after>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]",
                              item.title,
                              title,
                              item.page,
                              item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn print_reply(vec: &Vec<NodeType>,
               depth: u8,
               scrollY: usize,
               y: usize,
               rustbox: &rustbox::RustBox)
               -> usize {
    let padding = (0..depth).map(|_| "├─").collect::<Vec<_>>().join("");
    let mut m = 0;
    let mut recursive_offset = 0;
    let mut total_y = 0;
    let mut line = String::new();

    // clean up lines
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    for (j, node) in vec2.enumerate() {
        total_y = y + m + recursive_offset;
        if scrollY + 1 < total_y {
            let node2 = node.clone();
            match node2 {
                NodeType::Text(n) => {
                    if n.data != "" {
                        line = format!("{}{}", line, n.data);
                    }
                }
                NodeType::Image(n) => {
                    if n.data != "" {
                        line = format!("{}[img {}]", line, n.data);
                    }
                }
                NodeType::BlockQuote(n) => {
                    print_default(rustbox,
                                  0,
                                  total_y - scrollY,
                                  format!("{}[{}] = BlockQuote", padding, m));
                    m += 1;
                    recursive_offset += print_reply(&n.data,
                                                    depth + 1,
                                                    scrollY,
                                                    total_y + 1,
                                                    &rustbox);
                }
                NodeType::Br(n) => {
                    if !line.is_empty() {
                        print_default(rustbox,
                                      0,
                                      total_y - scrollY,
                                      format!(" {}{}", padding, line));
                        line = String::new();
                    }
                    m += 1;
                }
            }
        }
    }

    if !line.is_empty() {
        total_y = y + m + recursive_offset;
        print_default(rustbox,
                      0,
                      total_y - scrollY,
                      format!(" {}{}  ", padding, line));
        line = String::new();
        m += 1;
    }

    m + recursive_offset
}

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = print_reply(&reply.body, 0, scrollY, y, &rustbox);

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<|endoftext|>