impl Parseable trait

This commit is contained in:
John Turner
2025-10-28 09:44:32 +00:00
parent 92a8e46082
commit b54616a6dd
6 changed files with 367 additions and 272 deletions

View File

@@ -334,14 +334,14 @@ impl fmt::Display for Atom {
mod test {
use mon::{Parser, input::InputIter};
use crate::atom::parsers;
use super::*;
use crate::Parseable;
#[test]
fn test_version_display() {
let s = "1.0.0_alpha1_beta1-r1";
let version = parsers::version()
.parse_finished(InputIter::new(s))
.unwrap();
let version = Version::parser().parse_finished(InputIter::new(s)).unwrap();
assert_eq!(version.to_string().as_str(), s);
}
@@ -349,7 +349,7 @@ mod test {
#[test]
fn test_display_atom() {
let s = "!!>=foo/bar-1.0.0v_alpha1_beta1-r1:slot/sub=[a,b,c]";
let atom = parsers::atom().parse_finished(InputIter::new(s)).unwrap();
let atom = Atom::parser().parse_finished(InputIter::new(s)).unwrap();
assert_eq!(atom.to_string().as_str(), s);
}

View File

@@ -3,232 +3,291 @@ use core::option::Option::None;
use mon::{Parser, r#if, numeric1, one_of, tag};
use crate::{
Parseable,
atom::{
Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition,
UseDepNegate, UseDepSign, Version, VersionNumber, VersionOperator, VersionSuffix,
VersionSuffixKind,
},
useflag::parsers::useflag,
useflag::UseFlag,
};
pub fn blocker<'a>() -> impl Parser<&'a str, Output = Blocker> {
tag("!!")
.map(|_| Blocker::Strong)
.or(tag("!").map(|_| Blocker::Weak))
impl<'a> Parseable<'a, &'a str> for Blocker {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
tag("!!")
.map(|_| Blocker::Strong)
.or(tag("!").map(|_| Blocker::Weak))
}
}
pub fn version_operator<'a>() -> impl Parser<&'a str, Output = VersionOperator> {
tag("<=")
.map(|_| VersionOperator::LtEq)
.or(tag(">=").map(|_| VersionOperator::GtEq))
.or(tag("<").map(|_| VersionOperator::Lt))
.or(tag(">").map(|_| VersionOperator::Gt))
.or(tag("=").map(|_| VersionOperator::Eq))
.or(tag("~").map(|_| VersionOperator::Roughly))
impl<'a> Parseable<'a, &'a str> for VersionOperator {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
tag("<=")
.map(|_| VersionOperator::LtEq)
.or(tag(">=").map(|_| VersionOperator::GtEq))
.or(tag("<").map(|_| VersionOperator::Lt))
.or(tag(">").map(|_| VersionOperator::Gt))
.or(tag("=").map(|_| VersionOperator::Eq))
.or(tag("~").map(|_| VersionOperator::Roughly))
}
}
pub fn version_number<'a>() -> impl Parser<&'a str, Output = VersionNumber> {
numeric1()
.followed_by(tag("*").opt())
.recognize()
.map(|output: &str| VersionNumber(output.to_string()))
impl<'a> Parseable<'a, &'a str> for VersionNumber {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
numeric1()
.followed_by(tag("*").opt())
.recognize()
.map(|output: &str| VersionNumber(output.to_string()))
}
}
pub fn version_suffix_kind<'a>() -> impl Parser<&'a str, Output = VersionSuffixKind> {
tag("alpha")
.map(|_| VersionSuffixKind::Alpha)
.or(tag("beta").map(|_| VersionSuffixKind::Beta))
.or(tag("pre").map(|_| VersionSuffixKind::Pre))
.or(tag("rc").map(|_| VersionSuffixKind::Rc))
.or(tag("p").map(|_| VersionSuffixKind::P))
impl<'a> Parseable<'a, &'a str> for VersionSuffixKind {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
tag("alpha")
.map(|_| VersionSuffixKind::Alpha)
.or(tag("beta").map(|_| VersionSuffixKind::Beta))
.or(tag("pre").map(|_| VersionSuffixKind::Pre))
.or(tag("rc").map(|_| VersionSuffixKind::Rc))
.or(tag("p").map(|_| VersionSuffixKind::P))
}
}
pub fn version_suffix<'a>() -> impl Parser<&'a str, Output = VersionSuffix> {
version_suffix_kind()
.and(version_number().opt())
.map(|(kind, number)| VersionSuffix { kind, number })
impl<'a> Parseable<'a, &'a str> for VersionSuffix {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
VersionSuffixKind::parser()
.and(VersionNumber::parser().opt())
.map(|(kind, number)| VersionSuffix { kind, number })
}
}
pub fn version<'a>() -> impl Parser<&'a str, Output = Version> {
let numbers = version_number().separated_list(tag("."), 1..);
let suffixes = version_suffix().separated_list(tag("_"), 0..);
let rev = version_number().preceded_by(tag("-r"));
impl<'a> Parseable<'a, &'a str> for Version {
type Parser = impl Parser<&'a str, Output = Self>;
numbers
.and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt())
.and(suffixes.preceded_by(tag("_")).opt())
.and(rev.opt())
.map(|(((numbers, letter), suffixes), rev)| Version {
numbers,
letter,
suffixes: suffixes.unwrap_or(Vec::new()),
rev,
})
fn parser() -> Self::Parser {
let numbers = VersionNumber::parser().separated_list(tag("."), 1..);
let suffixes = VersionSuffix::parser().separated_list(tag("_"), 0..);
let rev = VersionNumber::parser().preceded_by(tag("-r"));
numbers
.and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt())
.and(suffixes.preceded_by(tag("_")).opt())
.and(rev.opt())
.map(|(((numbers, letter), suffixes), rev)| Version {
numbers,
letter,
suffixes: suffixes.unwrap_or(Vec::new()),
rev,
})
}
}
pub fn category<'a>() -> impl Parser<&'a str, Output = Category> {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_.-".contains(*c)).list(0..);
impl<'a> Parseable<'a, &'a str> for Category {
type Parser = impl Parser<&'a str, Output = Self>;
start
.and(rest)
.recognize()
.map(|output: &str| Category(output.to_string()))
fn parser() -> Self::Parser {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_.-".contains(*c)).list(0..);
start
.and(rest)
.recognize()
.map(|output: &str| Category(output.to_string()))
}
}
pub fn name<'a>() -> impl Parser<&'a str, Output = Name> {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "_+".contains(*c))
.or(one_of("-".chars()).and_not(
version().preceded_by(tag("-")).followed_by(
r#if(|c: &char| c.is_ascii_alphanumeric() || "_+-".contains(*c)).not(),
),
))
.list(0..);
impl<'a> Parseable<'a, &'a str> for Name {
type Parser = impl Parser<&'a str, Output = Self>;
start
.and(rest)
.recognize()
.map(|output: &str| Name(output.to_string()))
fn parser() -> Self::Parser {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "_+".contains(*c))
.or(
one_of("-".chars()).and_not(Version::parser().preceded_by(tag("-")).followed_by(
r#if(|c: &char| c.is_ascii_alphanumeric() || "_+-".contains(*c)).not(),
)),
)
.list(0..);
start
.and(rest)
.recognize()
.map(|output: &str| Name(output.to_string()))
}
}
pub fn slot_operator<'a>() -> impl Parser<&'a str, Output = SlotOperator> {
tag("=")
.map(|_| SlotOperator::Eq)
.or(tag("*").map(|_| SlotOperator::Star))
impl<'a> Parseable<'a, &'a str> for SlotOperator {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
tag("=")
.map(|_| SlotOperator::Eq)
.or(tag("*").map(|_| SlotOperator::Star))
}
}
pub fn slotname<'a>() -> impl Parser<&'a str, Output = SlotName> {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_.-".contains(*c)).list(0..);
impl<'a> Parseable<'a, &'a str> for SlotName {
type Parser = impl Parser<&'a str, Output = Self>;
start
.and(rest)
.recognize()
.map(|output: &str| SlotName(output.to_string()))
fn parser() -> Self::Parser {
let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_.-".contains(*c)).list(0..);
start
.and(rest)
.recognize()
.map(|output: &str| SlotName(output.to_string()))
}
}
pub fn slot<'a>() -> impl Parser<&'a str, Output = Slot> {
slotname()
.opt()
.and(slotname().preceded_by(tag("/")).opt())
.and(slot_operator().opt())
.map(|((slot, sub), operator)| Slot {
slot,
sub,
operator,
})
}
impl<'a> Parseable<'a, &'a str> for Slot {
type Parser = impl Parser<&'a str, Output = Self>;
pub fn usedep_sign<'a>() -> impl Parser<&'a str, Output = UseDepSign> {
tag("(-)")
.map(|_| UseDepSign::Disabled)
.or(tag("(+)").map(|_| UseDepSign::Enabled))
}
pub fn usedep<'a>() -> impl Parser<&'a str, Output = UseDep> {
let a = useflag()
.and(usedep_sign().opt())
.preceded_by(tag("-"))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Minus),
flag,
sign,
condition: None,
});
let b = useflag()
.and(usedep_sign().opt())
.preceded_by(tag("!"))
.followed_by(tag("?"))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Exclamation),
flag,
sign,
condition: Some(UseDepCondition::Question),
});
let c = useflag()
.and(usedep_sign().opt())
.followed_by(tag("?"))
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: Some(UseDepCondition::Question),
});
let d = useflag()
.and(usedep_sign().opt())
.preceded_by(tag("!"))
.followed_by(tag("="))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Exclamation),
flag,
sign,
condition: Some(UseDepCondition::Eq),
});
let e = useflag()
.and(usedep_sign().opt())
.followed_by(tag("="))
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: Some(UseDepCondition::Eq),
});
let f = useflag()
.and(usedep_sign().opt())
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: None,
});
a.or(b).or(c).or(d).or(e).or(f)
}
pub fn atom<'a>() -> impl Parser<&'a str, Output = Atom> {
blocker()
.opt()
.and(version_operator().opt())
.and(category())
.and(name().preceded_by(tag("/")))
.and(version().preceded_by(tag("-")).opt())
.and(slot().preceded_by(tag(":")).opt())
.and(
usedep()
.separated_list(tag(","), 0..)
.delimited_by(tag("["), tag("]"))
.opt(),
)
.map(
|((((((blocker, version_operator), category), name), version), slot), usedeps)| Atom {
blocker,
version_operator,
category,
name,
version,
fn parser() -> Self::Parser {
SlotName::parser()
.opt()
.and(SlotName::parser().preceded_by(tag("/")).opt())
.and(SlotOperator::parser().opt())
.map(|((slot, sub), operator)| Slot {
slot,
usedeps: usedeps.unwrap_or(Vec::new()),
},
)
.verify_output(|atom| match (&atom.version_operator, &atom.version) {
(Some(VersionOperator::Eq), Some(_)) => true,
(Some(_), Some(version))
if !version
.numbers()
.iter()
.any(|number| number.get().contains("*")) =>
{
true
}
(None, None) => true,
_ => false,
})
sub,
operator,
})
}
}
impl<'a> Parseable<'a, &'a str> for UseDepSign {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
tag("(-)")
.map(|_| UseDepSign::Disabled)
.or(tag("(+)").map(|_| UseDepSign::Enabled))
}
}
impl<'a> Parseable<'a, &'a str> for UseDep {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
let a = UseFlag::parser()
.and(UseDepSign::parser().opt())
.preceded_by(tag("-"))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Minus),
flag,
sign,
condition: None,
});
let b = UseFlag::parser()
.and(UseDepSign::parser().opt())
.preceded_by(tag("!"))
.followed_by(tag("?"))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Exclamation),
flag,
sign,
condition: Some(UseDepCondition::Question),
});
let c = UseFlag::parser()
.and(UseDepSign::parser().opt())
.followed_by(tag("?"))
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: Some(UseDepCondition::Question),
});
let d = UseFlag::parser()
.and(UseDepSign::parser().opt())
.preceded_by(tag("!"))
.followed_by(tag("="))
.map(|(flag, sign)| UseDep {
negate: Some(UseDepNegate::Exclamation),
flag,
sign,
condition: Some(UseDepCondition::Eq),
});
let e = UseFlag::parser()
.and(UseDepSign::parser().opt())
.followed_by(tag("="))
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: Some(UseDepCondition::Eq),
});
let f = UseFlag::parser()
.and(UseDepSign::parser().opt())
.map(|(flag, sign)| UseDep {
negate: None,
flag,
sign,
condition: None,
});
a.or(b).or(c).or(d).or(e).or(f)
}
}
impl<'a> Parseable<'a, &'a str> for Atom {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
Blocker::parser()
.opt()
.and(VersionOperator::parser().opt())
.and(Category::parser())
.and(Name::parser().preceded_by(tag("/")))
.and(Version::parser().preceded_by(tag("-")).opt())
.and(Slot::parser().preceded_by(tag(":")).opt())
.and(
UseDep::parser()
.separated_list(tag(","), 0..)
.delimited_by(tag("["), tag("]"))
.opt(),
)
.map(
|((((((blocker, version_operator), category), name), version), slot), usedeps)| {
Atom {
blocker,
version_operator,
category,
name,
version,
slot,
usedeps: usedeps.unwrap_or(Vec::new()),
}
},
)
.verify_output(|atom| match (&atom.version_operator, &atom.version) {
(Some(VersionOperator::Eq), Some(_)) => true,
(Some(_), Some(version))
if !version
.numbers()
.iter()
.any(|number| number.get().contains("*")) =>
{
true
}
(None, None) => true,
_ => false,
})
}
}
#[cfg(test)]
@@ -242,14 +301,14 @@ mod test {
fn test_version() {
let it = InputIter::new("1.0.0v_alpha1_beta1-r1");
version().check_finished(it).unwrap();
Version::parser().check_finished(it).unwrap();
}
#[test]
fn test_name() {
let it = InputIter::new("foo-1-bar-1.0.0");
match name().parse(it) {
match Name::parser().parse(it) {
Ok((_, output)) => {
assert_eq!(output.0.as_str(), "foo-1-bar");
}
@@ -263,7 +322,7 @@ mod test {
"!!>=cat/pkg-1-foo-1.0.0v_alpha1_p20250326-r1:primary/sub=[use,use=,!use=,use?,!use?,-use,use(+),use(-)]",
);
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
@@ -272,83 +331,83 @@ mod test {
"!!>=_.+-0-/_-test-T-123_beta1_-4a-6+-_p--1.00.02b_alpha3_pre_p4-r5:slot/_-+6-9=[test(+),test(-)]",
);
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_atom_with_star_in_non_empty_slot() {
let it = InputIter::new("foo/bar:*/subslot");
assert!(atom().check_finished(it).is_err());
assert!(Atom::parser().check_finished(it).is_err());
}
#[test]
fn test_invalid_usedep() {
let it = InputIter::new("foo-bar:slot/sub=[!use]");
assert!(atom().check_finished(it).is_err())
assert!(Atom::parser().check_finished(it).is_err())
}
#[test]
fn test_empty_slot() {
let it = InputIter::new("foo/bar:=");
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_usedep_with_underscore() {
let it = InputIter::new("foo/bar[use_dep]");
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_version_with_uppercase_letter() {
let it = InputIter::new("=foo/bar-1.0.0V");
assert!(atom().check_finished(it).is_err());
assert!(Atom::parser().check_finished(it).is_err());
}
#[test]
fn test_version_with_version_operator_without_version() {
let it = InputIter::new("=foo/bar");
assert!(atom().check_finished(it).is_err());
assert!(Atom::parser().check_finished(it).is_err());
}
#[test]
fn test_version_with_version_without_version_operator() {
let it = InputIter::new("foo/bar-1.0.0");
assert!(atom().check_finished(it).is_err());
assert!(Atom::parser().check_finished(it).is_err());
}
#[test]
fn test_atom_with_eq_version_operator() {
let it = InputIter::new("=foo/bar-1.0.0");
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_atom_with_star_in_version() {
let it = InputIter::new("=foo/bar-1.2*");
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_atom_with_star_in_version_without_eq_version_operator() {
let it = InputIter::new(">=foo/bar-1.2*");
assert!(atom().check_finished(it).is_err());
assert!(Atom::parser().check_finished(it).is_err());
}
#[test]
fn test_atom_with_trailing_dash_and_letter() {
let it = InputIter::new("dev-db/mysql-connector-c");
atom().check_finished(it).unwrap();
Atom::parser().check_finished(it).unwrap();
}
}