mccs-db 0.2.0

MCCS specification VCP database
Documentation
use {
    mccs::Version,
    nom::{
        branch::alt,
        bytes::complete::{tag, take_while_m_n},
        character::complete::{char, space0, u8},
        combinator::{map, map_res, opt},
        sequence::{delimited, preceded, separated_pair},
        Finish, IResult,
    },
    serde::{
        de::{Deserialize, Deserializer, Error, Unexpected},
        ser::{Serialize, Serializer},
    },
    std::{cmp, fmt},
};

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Req<V> {
    Bracket(Box<Req<V>>),
    And(Box<Req<V>>, Box<Req<V>>),
    Or(Box<Req<V>>, Box<Req<V>>),
    Eq(V),
    Ge(V),
    Le(V),
    Gt(V),
    Lt(V),
}

impl<V: fmt::Display> fmt::Display for Req<V> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Req::Bracket(r) => write!(f, "({r})"),
            Req::And(lhs, rhs) => write!(f, "{lhs} && {rhs}"),
            Req::Or(lhs, rhs) => write!(f, "{lhs} || {rhs}"),
            Req::Eq(v) => write!(f, "={v}"),
            Req::Gt(v) => write!(f, ">{v}"),
            Req::Ge(v) => write!(f, ">={v}"),
            Req::Lt(v) => write!(f, "<{v}"),
            Req::Le(v) => write!(f, "<={v}"),
        }
    }
}

pub(crate) type VersionReq = Req<Version>;

trait ReqValue: Sized + fmt::Debug + fmt::Display {
    type Err: fmt::Debug;
    const EXPECTED: &'static str;

    fn parse_req(req: &str) -> Result<Req<Self>, Self::Err>;
    fn parse_nom(i: &str) -> IResult<&str, Self>;
}

impl ReqValue for Version {
    type Err = nom::error::ErrorKind;

    const EXPECTED: &'static str = "version requirement";

    fn parse_req(req: &str) -> Result<Req<Self>, Self::Err> {
        parse_req_expr(req).finish().map(|v| v.1).map_err(|e| e.code)
    }

    fn parse_nom(i: &str) -> IResult<&str, Self> {
        parse_version(i)
    }
}

impl ReqValue for u8 {
    type Err = nom::error::ErrorKind;

    const EXPECTED: &'static str = "version requirement";

    fn parse_req(req: &str) -> Result<Req<Self>, Self::Err> {
        parse_req_expr(req).finish().map(|v| v.1).map_err(|e| e.code)
    }

    fn parse_nom(i: &str) -> IResult<&str, Self> {
        parse_u8(i)
    }
}

impl<'a, V: ReqValue> Deserialize<'a> for Req<V> {
    fn deserialize<D: Deserializer<'a>>(d: D) -> Result<Self, D::Error> {
        String::deserialize(d).and_then(|v| {
            V::parse_req(&v).map_err(|e| D::Error::invalid_value(Unexpected::Other(&format!("{:?}", e)), &V::EXPECTED))
        })
    }
}

impl<V: ReqValue> Serialize for Req<V> {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        self.to_string().serialize(s)
    }
}

impl<V> Req<V> {
    #[cfg(test)]
    fn and<R: Into<Box<Self>>>(self, rhs: R) -> Self {
        Req::And(self.into(), rhs.into())
    }

    #[cfg(test)]
    fn or<R: Into<Box<Self>>>(self, rhs: R) -> Self {
        Req::Or(self.into(), rhs.into())
    }
}

impl<V> Req<V>
where
    V: cmp::PartialEq + cmp::PartialOrd,
{
    pub fn matches(&self, v: &V) -> bool {
        match *self {
            Req::Bracket(ref r) => r.matches(v),
            Req::And(ref lhs, ref rhs) => lhs.matches(v) && rhs.matches(v),
            Req::Or(ref lhs, ref rhs) => lhs.matches(v) || rhs.matches(v),
            Req::Eq(ref req) => v == req,
            Req::Ge(ref req) => v >= req,
            Req::Le(ref req) => v <= req,
            Req::Lt(ref req) => v < req,
            Req::Gt(ref req) => v > req,
        }
    }
}

impl Default for Req<Version> {
    fn default() -> Self {
        Req::Ge(Version::new(0, 0))
    }
}

fn parse_req_expr<V: ReqValue>(i: &str) -> IResult<&str, Req<V>> {
    let (i, lhs) = match opt(delimited(char('('), parse_req_expr, char(')')))(i)? {
        (i, Some(req)) => (i, Req::Bracket(Box::new(req))),
        (_, None) => parse_req(i)?,
    };

    let (i, _) = space0(i)?;
    let tags = alt((tag("&&"), tag("||")));
    match opt(delimited(space0, tags, space0))(i)? {
        (i, Some(op)) => {
            let (i, rhs) = parse_req_expr(i)?;
            Ok((i, match op {
                "&&" => Req::And(Box::new(lhs), Box::new(rhs)),
                "||" => Req::Or(Box::new(lhs), Box::new(rhs)),
                op => unreachable!("{lhs:?} {op} {rhs:?}"),
            }))
        },
        (i, None) => Ok((i, lhs)),
    }
}

fn parse_req<V: ReqValue>(i: &str) -> IResult<&str, Req<V>> {
    let (i, _) = space0(i)?;
    let tags = alt((tag("<="), tag("<"), tag(">="), tag(">"), tag("=")));
    let op: Option<(_, fn(V) -> Req<_>)> = match opt(tags)(i)? {
        (i, Some(op)) => Some((i, match op {
            "<=" => Req::Le,
            "<" => Req::Lt,
            ">=" => Req::Ge,
            ">" => Req::Gt,
            "=" => Req::Eq,
            op => unreachable!("unknown op {op}"),
        })),
        (_, None) => None,
    };
    match op {
        Some((i, op)) => map(V::parse_nom, op)(i),
        None => map(V::parse_nom, Req::Eq)(i),
    }
}

fn hex_u8(i: &str) -> IResult<&str, u8> {
    map_res(take_while_m_n(1, 2, |c: char| c.is_digit(16)), |i| {
        u8::from_str_radix(i, 16)
    })(i)
}

fn parse_u8(i: &str) -> IResult<&str, u8> {
    trim_spaces(alt((preceded(tag("0x"), hex_u8), u8)))(i)
}

fn parse_version(i: &str) -> IResult<&str, Version> {
    trim_spaces(map(separated_pair(u8, trim_spaces(char('.')), u8), |(major, minor)| {
        Version { major, minor }
    }))(i)
}

fn trim_spaces<I, O, E, P>(parser: P) -> impl FnMut(I) -> nom::IResult<I, O, E>
where
    P: nom::Parser<I, O, E>,
    E: nom::error::ParseError<I>,
    I: Clone + nom::InputTakeAtPosition,
    <I as nom::InputTakeAtPosition>::Item: nom::AsChar + Clone,
{
    delimited(space0, parser, space0)
}

#[test]
fn version() {
    assert_eq!(parse_version("22.01").finish().unwrap().1, Version {
        major: 22,
        minor: 1
    })
}

#[test]
fn version_eq() {
    assert_eq!(
        parse_req_expr("2.0").finish().unwrap().1,
        VersionReq::Eq(Version { major: 2, minor: 0 })
    )
}

#[test]
fn version_req() {
    assert_eq!(
        parse_req_expr("<=3.1").finish().unwrap().1,
        VersionReq::Le(Version { major: 3, minor: 1 })
    )
}

#[test]
fn u8_req() {
    assert_eq!(parse_req_expr("<=0x0a").finish().unwrap().1, Req::Le(0xa))
}

#[test]
fn u8_expr() {
    assert_eq!(
        parse_req_expr("0x0a && <0x05").finish().unwrap().1,
        Req::Eq(0xa).and(Req::Lt(5))
    )
}

#[test]
fn u8_chained_expr() {
    assert_eq!(
        parse_req_expr("(0x0a && <0x05) || (0x01 && 0x02)").finish().unwrap().1,
        Req::Bracket(Req::Eq(0xa).and(Req::Lt(5)).into()).or(Req::Bracket(Req::Eq(1).and(Req::Eq(2)).into()))
    )
}

#[test]
fn u8_ordered_chain() {
    assert_eq!(
        parse_req_expr("0x0a || 0x01 || 0x02").finish().unwrap().1,
        Req::Eq(0xa).or(Req::Eq(1).or(Req::Eq(2)))
    )
}

#[test]
fn u8_nested_expr() {
    assert_eq!(
        parse_req_expr("((0x0a && (0x01 && 0x02)) || <0x03)")
            .finish()
            .unwrap()
            .1,
        Req::Bracket(
            Req::Bracket(Req::Eq(0xa).and(Req::Bracket(Req::Eq(1).and(Req::Eq(2)).into())).into())
                .or(Req::Lt(3))
                .into()
        )
    )
}

#[test]
fn u8_input_select_expr() {
    assert_eq!(
        parse_req_expr("(>=0x13 && <=0x18) || =0x1A || >=0x1C")
            .finish()
            .unwrap()
            .1,
        Req::Bracket(Req::Ge(0x13).and(Req::Le(0x18)).into()).or(Req::Eq(0x1a).or(Req::Ge(0x1c)))
    )
}