feature_check/expr/parser/
p_nom.rs1use std::collections::HashMap;
6use std::iter;
7
8use anyhow::Context as _;
9use nom::{
10 Err as NErr, IResult, Parser as _, branch as nbranch,
11 bytes::complete as nbytesc,
12 character::complete as ncharc,
13 combinator as ncomb,
14 error::{Error as NError, ErrorKind as NErrorKind},
15 multi as nmulti, sequence as nseq,
16};
17
18use crate::defs::{Mode, ParseError};
19use crate::expr::{BoolOp, BoolOpKind, FeatureOp, VersionOp};
20use crate::version::{ParseError as VParseError, Version, VersionComponent};
21
22#[inline]
24fn err_fail(input: &str) -> NErr<NError<&str>> {
25 NErr::Failure(NError::new(input, NErrorKind::Fail))
26}
27
28fn clone_err_input(err: NErr<NError<&str>>) -> NErr<NError<String>> {
30 err.map_input(ToOwned::to_owned)
31}
32
33#[inline]
40fn v_num(input: &str) -> IResult<&str, u32> {
41 let (r_input, digits) = nbytesc::take_while1(|chr: char| chr.is_ascii_digit())(input)?;
42 #[expect(clippy::map_err_ignore, reason = "it really does not matter")]
43 Ok((r_input, digits.parse::<u32>().map_err(|_| err_fail(input))?))
44}
45
46#[inline]
53fn v_rest(input: &str) -> IResult<&str, &str> {
54 let (f_input, _) = ncharc::one_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrwxyz~+")(input)?;
55 let (r_input, _) = nbytesc::take_while(|chr| {
56 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~+".contains(chr)
57 })(f_input)?;
58 Ok((
59 r_input,
60 input.strip_suffix(r_input).ok_or_else(|| err_fail(input))?,
61 ))
62}
63
64#[inline]
71fn v_comp_with_num(input: &str) -> IResult<&str, VersionComponent> {
72 let (r_input, (num, rest)) = nseq::pair(v_num, ncomb::opt(v_rest)).parse(input)?;
73 Ok((
74 r_input,
75 VersionComponent {
76 num: Some(num),
77 rest: rest.map_or_else(String::new, str::to_owned),
78 },
79 ))
80}
81
82#[inline]
88fn v_comp_rest_only(input: &str) -> IResult<&str, VersionComponent> {
89 let (r_input, rest) = v_rest(input)?;
90 Ok((
91 r_input,
92 VersionComponent {
93 num: None,
94 rest: rest.to_owned(),
95 },
96 ))
97}
98
99#[inline]
105fn v_components(input: &str) -> IResult<&str, Vec<VersionComponent>> {
106 let (r_input, (first, arr)) = nseq::pair(
107 nbranch::alt((v_comp_with_num, v_comp_rest_only)),
108 ncomb::opt(nmulti::many0(nseq::pair(
109 nbytesc::tag("."),
110 nbranch::alt((v_comp_with_num, v_comp_rest_only)),
111 ))),
112 )
113 .parse(input)?;
114 if let Some(comps) = arr {
115 Ok((
116 r_input,
117 iter::once(first)
118 .chain(comps.into_iter().map(|(_dot, comp)| comp))
119 .collect(),
120 ))
121 } else {
122 Ok((r_input, vec![first]))
123 }
124}
125
126#[inline]
133fn p_version(input: &str) -> IResult<&str, Version> {
134 let (r_input, comps) = v_components(input)?;
135 let v_chars = input.strip_suffix(r_input).ok_or_else(|| err_fail(input))?;
136 Ok((r_input, Version::new(String::from(v_chars), comps)))
137}
138
139#[inline]
145pub fn parse_version(value: &str) -> Result<Version, VParseError> {
146 let (left, res) = p_version(value)
147 .map_err(clone_err_input)
148 .context("Could not parse a version string")
149 .map_err(|err| VParseError::ParseFailure(value.to_owned(), err))?;
150 if left.is_empty() {
151 Ok(res)
152 } else {
153 Err(VParseError::ParseLeftovers(value.to_owned(), left.len()))
154 }
155}
156
157#[inline]
163fn p_feature(input: &str) -> IResult<&str, &str> {
164 let (r_input, name) =
165 nbytesc::is_a("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-")(input)?;
166 Ok((r_input, name))
167}
168
169#[inline]
176fn p_op_sign(input: &str) -> IResult<&str, BoolOpKind> {
177 let (r_input, res) = nbranch::alt((
178 nbytesc::tag(BoolOpKind::LE),
179 nbytesc::tag(BoolOpKind::LT),
180 nbytesc::tag(BoolOpKind::EQ),
181 nbytesc::tag(BoolOpKind::GE),
182 nbytesc::tag(BoolOpKind::GT),
183 ))
184 .parse(input)?;
185 #[expect(clippy::map_err_ignore, reason = "it really does not matter")]
186 Ok((r_input, res.parse().map_err(|_| err_fail(input))?))
187}
188
189#[inline]
196fn p_op_word(input: &str) -> IResult<&str, BoolOpKind> {
197 let (r_input, res) = nbranch::alt((
198 nbytesc::tag(BoolOpKind::LT_S),
199 nbytesc::tag(BoolOpKind::LE_S),
200 nbytesc::tag(BoolOpKind::EQ_S),
201 nbytesc::tag(BoolOpKind::GE_S),
202 nbytesc::tag(BoolOpKind::GT_S),
203 ))
204 .parse(input)?;
205 #[expect(clippy::map_err_ignore, reason = "it really does not matter")]
206 Ok((r_input, res.parse().map_err(|_| err_fail(input))?))
207}
208
209#[inline]
215fn p_op_sign_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
216 let (r_input, res) = (
217 ncharc::multispace0,
218 p_op_sign,
219 ncharc::multispace0,
220 p_version,
221 ncharc::multispace0,
222 )
223 .parse(input)?;
224 Ok((r_input, (res.1, res.3)))
225}
226
227#[inline]
233fn p_op_word_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
234 let (r_input, res) = (
235 ncharc::multispace1,
236 p_op_word,
237 ncharc::multispace1,
238 p_version,
239 ncharc::multispace0,
240 )
241 .parse(input)?;
242 Ok((r_input, (res.1, res.3)))
243}
244
245#[inline]
251fn p_op_and_version(input: &str) -> IResult<&str, (BoolOpKind, Version)> {
252 nbranch::alt((p_op_sign_and_version, p_op_word_and_version)).parse(input)
253}
254
255#[inline]
261fn p_expr(input: &str) -> IResult<&str, Mode> {
262 let (r_input, (feature, op_ver)) =
263 nseq::pair(p_feature, ncomb::opt(p_op_and_version)).parse(input)?;
264 if let Some((op, ver)) = op_ver {
265 Ok((
266 r_input,
267 Mode::Simple(Box::new(BoolOp::new(
268 op,
269 Box::new(FeatureOp::new(feature)),
270 Box::new(VersionOp::from_version(ver)),
271 ))),
272 ))
273 } else {
274 Ok((r_input, Mode::Single(Box::new(FeatureOp::new(feature)))))
275 }
276}
277
278#[inline]
285fn p_feature_version(input: &str) -> IResult<&str, (String, Version)> {
286 let (r_input, (feature, version)) = nseq::pair(
287 p_feature,
288 ncomb::opt(nseq::pair(nbytesc::tag("="), p_version)),
289 )
290 .parse(input)?;
291 #[expect(clippy::map_err_ignore, reason = "it really does not matter")]
292 Ok((
293 r_input,
294 (
295 feature.to_owned(),
296 version.map_or_else(
297 || parse_version("1.0").map_err(|_| err_fail(input)),
298 |(_, ver)| Ok(ver),
299 )?,
300 ),
301 ))
302}
303
304#[inline]
310fn p_features_line(input: &str) -> IResult<&str, HashMap<String, Version>> {
311 let (r_input, (_, first, rest, _)) = (
312 ncharc::multispace0,
313 p_feature_version,
314 nmulti::many0(nseq::pair(ncharc::multispace1, p_feature_version)),
315 ncharc::multispace0,
316 )
317 .parse(input)?;
318 Ok((
319 r_input,
320 iter::once(first)
321 .chain(rest.into_iter().map(|(_, pair)| pair))
322 .collect(),
323 ))
324}
325
326#[inline]
332pub fn parse_expr(expr: &str) -> Result<Mode, ParseError> {
333 let (left, mode) = p_expr(expr)
334 .map_err(clone_err_input)
335 .context("Could not parse a test expression")
336 .map_err(|err| ParseError::ParseFailure(expr.to_owned(), err))?;
337 if left.is_empty() {
338 Ok(mode)
339 } else {
340 Err(ParseError::ParseLeftovers(expr.to_owned(), left.len()))
341 }
342}
343
344#[inline]
350pub fn parse_features_line(line: &str) -> Result<HashMap<String, Version>, ParseError> {
351 let (left, res) = p_features_line(line)
352 .map_err(clone_err_input)
353 .context("Could not parse the program's features line")
354 .map_err(|err| ParseError::ParseFailure(line.to_owned(), err))?;
355 if left.is_empty() {
356 Ok(res)
357 } else {
358 Err(ParseError::ParseLeftovers(line.to_owned(), left.len()))
359 }
360}