use std::collections::HashMap;
use std::iter;
use anyhow::Context;
use nom::{
branch as nbranch,
bytes::complete as nbytesc,
character::complete as ncharc,
combinator as ncomb,
error::{Error as NError, ErrorKind as NErrorKind},
multi as nmulti, sequence as nseq, Err as NErr, IResult,
};
use crate::defs::{Mode, ParseError};
use crate::expr::{BoolOp, BoolOpKind, FeatureOp, VersionOp};
use crate::version::{ParseError as VParseError, Version, VersionComponent};
#[inline]
fn err_fail(input: &str) -> NErr<NError<&str>> {
NErr::Failure(NError::new(input, NErrorKind::Fail))
}
fn clone_err_input(err: NErr<NError<&str>>) -> NErr<NError<String>> {
err.map_input(std::borrow::ToOwned::to_owned)
}
#[allow(clippy::map_err_ignore)]
#[inline]
fn v_num(input: &str) -> IResult<&str, u32> {
let (r_input, digits) = nbytesc::take_while1(|c: char| c.is_ascii_digit())(input)?;
Ok((r_input, digits.parse::<u32>().map_err(|_| err_fail(input))?))
}
#[inline]
fn v_rest(input: &str) -> IResult<&str, &str> {
let (f_input, _) = ncharc::one_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrwxyz~+")(input)?;
let (r_input, _) = nbytesc::take_while(|c| {
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~+".contains(c)
})(f_input)?;
Ok((
r_input,
input.strip_suffix(r_input).ok_or_else(|| err_fail(input))?,
))
}
#[inline]
fn v_comp_with_num(input: &str) -> IResult<&str, VersionComponent> {
let (r_input, (num, rest)) = nseq::pair(v_num, ncomb::opt(v_rest))(input)?;
Ok((
r_input,
VersionComponent {
num: Some(num),
rest: rest.map_or_else(String::new, str::to_owned),
},
))
}
#[inline]
fn v_comp_rest_only(input: &str) -> IResult<&str, VersionComponent> {
let (r_input, rest) = v_rest(input)?;
Ok((
r_input,
VersionComponent {
num: None,
rest: rest.to_owned(),
},
))
}
#[inline]
fn v_components(input: &str) -> IResult<&str, Vec<VersionComponent>> {
let (r_input, (first, arr)) = nseq::pair(
nbranch::alt((v_comp_with_num, v_comp_rest_only)),
ncomb::opt(nmulti::many0(nseq::pair(
nbytesc::tag("."),
nbranch::alt((v_comp_with_num, v_comp_rest_only)),
))),
)(input)?;
if let Some(comps) = arr {
Ok((
r_input,
iter::once(first)
.chain(comps.into_iter().map(|(_dot, comp)| comp))
.collect(),
))
} else {
Ok((r_input, vec![first]))
}
}
#[inline]
fn p_version(input: &str) -> IResult<&str, Version> {
let (r_input, comps) = v_components(input)?;
let v_chars = input.strip_suffix(r_input).ok_or_else(|| err_fail(input))?;
Ok((r_input, Version::new(String::from(v_chars), comps)))
}
#[inline]
pub fn parse_version(s: &str) -> Result<Version, VParseError> {
let (left, res) = p_version(s)
.map_err(clone_err_input)
.context("Could not parse a version string")
.map_err(|err| VParseError::ParseFailure(s.to_owned(), err))?;
if left.is_empty() {
Ok(res)
} else {
Err(VParseError::ParseLeftovers(s.to_owned(), left.len()))
}
}
#[inline]
fn p_feature(input: &str) -> IResult<&str, &str> {
let (r_input, name) =
nbytesc::is_a("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-")(input)?;
Ok((r_input, name))
}
#[allow(clippy::map_err_ignore)]
#[inline]
fn p_op_sign(input: &str) -> IResult<&str, BoolOpKind> {
let (r_input, res) = nbranch::alt((
nbytesc::tag(BoolOpKind::LE),
nbytesc::tag(BoolOpKind::LT),
nbytesc::tag(BoolOpKind::EQ),
nbytesc::tag(BoolOpKind::GE),
nbytesc::tag(BoolOpKind::GT),
))(input)?;
Ok((r_input, res.parse().map_err(|_| err_fail(input))?))
}
#[allow(clippy::map_err_ignore)]
#[inline]
fn p_op_word(input: &str) -> IResult<&str, BoolOpKind> {
let (r_input, res) = nbranch::alt((
nbytesc::tag(BoolOpKind::LT_S),
nbytesc::tag(BoolOpKind::LE_S),
nbytesc::tag(BoolOpKind::EQ_S),
nbytesc::tag(BoolOpKind::GE_S),
nbytesc::tag(BoolOpKind::GT_S),
))(input)?;
Ok((r_input, res.parse().map_err(|_| err_fail(input))?))
}
#[inline]
fn p_op_sign_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
let (r_input, res) = nseq::tuple((
ncharc::multispace0,
p_op_sign,
ncharc::multispace0,
p_version,
ncharc::multispace0,
))(input)?;
Ok((r_input, (res.1, res.3)))
}
#[inline]
fn p_op_word_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
let (r_input, res) = nseq::tuple((
ncharc::multispace1,
p_op_word,
ncharc::multispace1,
p_version,
ncharc::multispace0,
))(input)?;
Ok((r_input, (res.1, res.3)))
}
#[inline]
fn p_op_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
nbranch::alt((p_op_sign_and_version, p_op_word_and_version))(input)
}
#[inline]
fn p_expr(input: &str) -> IResult<&str, Mode> {
let (r_input, (feature, op_ver)) = nseq::pair(p_feature, ncomb::opt(p_op_and_version))(input)?;
if let Some((op, ver)) = op_ver {
Ok((
r_input,
Mode::Simple(Box::new(BoolOp::new(
op,
Box::new(FeatureOp::new(feature)),
Box::new(VersionOp::from_version(ver)),
))),
))
} else {
Ok((r_input, Mode::Single(Box::new(FeatureOp::new(feature)))))
}
}
#[allow(clippy::map_err_ignore)]
#[inline]
fn p_feature_version(input: &str) -> IResult<&str, (String, Version)> {
let (r_input, (feature, version)) = nseq::pair(
p_feature,
ncomb::opt(nseq::pair(nbytesc::tag("="), p_version)),
)(input)?;
Ok((
r_input,
(
feature.to_owned(),
version.map_or_else(
|| parse_version("1.0").map_err(|_| err_fail(input)),
|(_, ver)| Ok(ver),
)?,
),
))
}
#[inline]
fn p_features_line(input: &str) -> IResult<&str, HashMap<String, Version>> {
let (r_input, (_, first, rest, _)) = nseq::tuple((
ncharc::multispace0,
p_feature_version,
nmulti::many0(nseq::pair(ncharc::multispace1, p_feature_version)),
ncharc::multispace0,
))(input)?;
Ok((
r_input,
iter::once(first)
.chain(rest.into_iter().map(|(_, pair)| pair))
.collect(),
))
}
#[inline]
pub fn parse_expr(expr: &str) -> Result<Mode, ParseError> {
let (left, mode) = p_expr(expr)
.map_err(clone_err_input)
.context("Could not parse a test expression")
.map_err(|err| ParseError::ParseFailure(expr.to_owned(), err))?;
if left.is_empty() {
Ok(mode)
} else {
Err(ParseError::ParseLeftovers(expr.to_owned(), left.len()))
}
}
#[inline]
pub fn parse_features_line(s: &str) -> Result<HashMap<String, Version>, ParseError> {
let (left, res) = p_features_line(s)
.map_err(clone_err_input)
.context("Could not parse the program's features line")
.map_err(|err| ParseError::ParseFailure(s.to_owned(), err))?;
if left.is_empty() {
Ok(res)
} else {
Err(ParseError::ParseLeftovers(s.to_owned(), left.len()))
}
}