From b54616a6dd4f512b4ec87c3780c36257e5c322b3 Mon Sep 17 00:00:00 2001 From: John Turner Date: Tue, 28 Oct 2025 09:44:32 +0000 Subject: [PATCH] impl Parseable trait --- src/atom/mod.rs | 10 +- src/atom/parsers.rs | 475 +++++++++++++++++++++++------------------ src/depend/parsers.rs | 86 ++++---- src/lib.rs | 9 + src/useflag/parsers.rs | 49 +++-- tests/depend.rs | 10 +- 6 files changed, 367 insertions(+), 272 deletions(-) diff --git a/src/atom/mod.rs b/src/atom/mod.rs index 11c56f8..ffb9c61 100644 --- a/src/atom/mod.rs +++ b/src/atom/mod.rs @@ -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); } diff --git a/src/atom/parsers.rs b/src/atom/parsers.rs index 3096e67..35df5f8 100644 --- a/src/atom/parsers.rs +++ b/src/atom/parsers.rs @@ -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(); } } diff --git a/src/depend/parsers.rs b/src/depend/parsers.rs index 10da058..7a69fee 100644 --- a/src/depend/parsers.rs +++ b/src/depend/parsers.rs @@ -1,61 +1,73 @@ -use mon::{Parser, ParserResult, input::InputIter, tag, whitespace1}; +use mon::{Parser, tag, whitespace1}; use crate::{ - atom, + Parseable, + atom::Atom, depend::{Conditional, Expr}, - useflag, + useflag::UseFlag, }; -fn expr(it: InputIter<&str>) -> ParserResult<&str, Expr> { - let all_of = expr - .separated_list(whitespace1(), 1..) - .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) - .map(|exprs| Expr::AllOf(exprs)); +impl<'a> Parseable<'a, &'a str> for Expr { + type Parser = impl Parser<&'a str, Output = Self>; - let any_of = expr - .separated_list(whitespace1(), 1..) - .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) - .preceded_by(tag("||").followed_by(whitespace1())) - .map(|exprs| Expr::AnyOf(exprs)); + fn parser() -> Self::Parser { + |it| { + let all_of = Expr::parser() + .separated_list(whitespace1(), 1..) + .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) + .map(|exprs| Expr::AllOf(exprs)); - let one_of = expr - .separated_list(whitespace1(), 1..) - .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) - .preceded_by(tag("^^").followed_by(whitespace1())) - .map(|exprs| Expr::OneOf(exprs)); + let any_of = Expr::parser() + .separated_list(whitespace1(), 1..) + .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) + .preceded_by(tag("||").followed_by(whitespace1())) + .map(|exprs| Expr::AnyOf(exprs)); - atom::parsers::atom() - .map(|atom| Expr::Atom(atom)) - .or(conditional().map(|conditional| Expr::Conditional(conditional))) - .or(any_of) - .or(all_of) - .or(one_of) - .parse(it) + let one_of = Expr::parser() + .separated_list(whitespace1(), 1..) + .delimited_by(tag("(").followed_by(whitespace1()), tag(")")) + .preceded_by(tag("^^").followed_by(whitespace1())) + .map(|exprs| Expr::OneOf(exprs)); + + Atom::parser() + .map(|atom| Expr::Atom(atom)) + .or(Conditional::parser().map(|conditional| Expr::Conditional(conditional))) + .or(any_of) + .or(all_of) + .or(one_of) + .parse(it) + } + } } -fn conditional<'a>() -> impl Parser<&'a str, Output = Conditional> { - useflag::parsers::useflag() - .preceded_by(tag("!")) - .followed_by(tag("?")) - .map(|flag| Conditional::Negative(flag)) - .or(useflag::parsers::useflag() +impl<'a> Parseable<'a, &'a str> for Conditional { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("!")) .followed_by(tag("?")) - .map(|flag| Conditional::Positive(flag))) -} - -pub fn exprs<'a>() -> impl Parser<&'a str, Output = Vec> { - expr.separated_list(whitespace1(), 0..) + .map(|flag| Conditional::Negative(flag)) + .or(UseFlag::parser() + .followed_by(tag("?")) + .map(|flag| Conditional::Positive(flag))) + } } #[cfg(test)] mod test { + use mon::input::InputIter; + use super::*; #[test] fn test_expr() { let it = InputIter::new("flag? ( || ( foo/bar foo/bar ) )"); - exprs().check_finished(it).unwrap(); + Expr::parser() + .separated_list(whitespace1(), 0..) + .check_finished(it) + .unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index 4a1364e..d986f3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,14 @@ #![deny(clippy::pedantic)] #![allow(dead_code, unstable_name_collisions)] +#![feature(impl_trait_in_assoc_type)] + +use mon::{Parser, input::Input}; + +pub trait Parseable<'a, I: Input + 'a> { + type Parser: Parser; + + fn parser() -> Self::Parser; +} pub mod atom; pub mod depend; diff --git a/src/useflag/parsers.rs b/src/useflag/parsers.rs index 49000d9..6b13a64 100644 --- a/src/useflag/parsers.rs +++ b/src/useflag/parsers.rs @@ -1,26 +1,37 @@ use mon::{Parser, r#if, tag}; -use crate::useflag::{IUseFlag, UseFlag}; +use crate::{ + Parseable, + useflag::{IUseFlag, UseFlag}, +}; -pub fn useflag<'a>() -> impl Parser<&'a str, Output = UseFlag> { - let start = r#if(|c: &char| c.is_ascii_alphanumeric()); - let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_@-".contains(*c)).list(0..); +impl<'a> Parseable<'a, &'a str> for UseFlag { + type Parser = impl Parser<&'a str, Output = Self>; - start - .and(rest) - .recognize() - .map(|output: &str| UseFlag(output.to_string())) + fn parser() -> Self::Parser { + let start = r#if(|c: &char| c.is_ascii_alphanumeric()); + let rest = r#if(|c: &char| c.is_ascii_alphanumeric() || "+_@-".contains(*c)).list(0..); + + start + .and(rest) + .recognize() + .map(|output: &str| UseFlag(output.to_string())) + } } -pub fn iuseflag<'a>() -> impl Parser<&'a str, Output = IUseFlag> { - useflag() - .preceded_by(tag("+")) - .map(|flag| IUseFlag { - default: true, - flag, - }) - .or(useflag().map(|flag| IUseFlag { - default: false, - flag, - })) +impl<'a> Parseable<'a, &'a str> for IUseFlag { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("+")) + .map(|flag| IUseFlag { + default: true, + flag, + }) + .or(UseFlag::parser().map(|flag| IUseFlag { + default: false, + flag, + })) + } } diff --git a/tests/depend.rs b/tests/depend.rs index 085d739..12d6e75 100644 --- a/tests/depend.rs +++ b/tests/depend.rs @@ -1,5 +1,8 @@ -use gentoo_utils::depend; -use mon::{Parser, eof, input::InputIter, tag}; +use gentoo_utils::{ + Parseable, + depend::{self, Expr}, +}; +use mon::{Parser, eof, input::InputIter, tag, whitespace1}; use std::fs; #[test] @@ -14,7 +17,8 @@ fn parse_md5_cache() { if line.starts_with("DEPEND=") { eprintln!("{line}"); eprintln!(); - depend::parsers::exprs() + Expr::parser() + .separated_list(whitespace1(), 0..) .ignore() .or(eof()) .preceded_by(tag("DEPEND="))