From 2e7d8cfbb92361b6c35f8f9a567c0517139cfb51 Mon Sep 17 00:00:00 2001 From: John Turner Date: Thu, 23 Oct 2025 00:52:35 -0400 Subject: [PATCH] impl atom parsing --- src/atom/mod.rs | 106 +++++++++++++++++++ src/atom/parsers.rs | 227 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 + src/useflag/mod.rs | 10 ++ src/useflag/parsers.rs | 10 ++ 5 files changed, 358 insertions(+) create mode 100644 src/atom/mod.rs create mode 100644 src/atom/parsers.rs create mode 100644 src/lib.rs create mode 100644 src/useflag/mod.rs create mode 100644 src/useflag/parsers.rs diff --git a/src/atom/mod.rs b/src/atom/mod.rs new file mode 100644 index 0000000..f5884bc --- /dev/null +++ b/src/atom/mod.rs @@ -0,0 +1,106 @@ +use core::option::Option; + +use crate::useflag::UseFlag; +use get::Get; + +pub mod parsers; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Blocker { + Weak, + Strong, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VersionOperator { + Lt, + Gt, + LtEq, + GtEq, + Roughly, +} + +#[derive(Clone, Debug, PartialEq, Eq, Get)] +pub struct Category(#[get(method = "get")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Get)] +pub struct Name(#[get(method = "get")] String); + +#[derive(Clone, Debug, Get)] +pub struct VersionNumber(#[get(method = "get")] String); + +#[derive(Clone, Copy, Debug)] +pub enum VersionSuffixKind { + Alpha, + Beta, + Pre, + Rc, + P, +} + +#[derive(Clone, Debug, Get)] +pub struct VersionSuffix { + kind: VersionSuffixKind, + number: Option, +} + +#[derive(Clone, Debug, Get)] +pub struct Version { + numbers: Vec, + letter: Option, + suffixes: Vec, + rev: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SlotOperator { + Eq, + Star, +} + +#[derive(Clone, Debug, PartialEq, Eq, Get)] +pub struct SlotName(#[get(method = "name")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Get)] +pub struct Slot { + slot: SlotName, + sub: Option, + operator: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UseDepNegate { + Minus, + Exclamation, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UseDepSign { + Enabled, + Disabled, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UseDepCondition { + Eq, + Question, +} + +#[derive(Clone, Debug, PartialEq, Eq, Get)] +pub struct UseDep { + negate: Option, + flag: UseFlag, + sign: Option, + condition: Option, +} + +#[derive(Clone, Debug, Get)] +pub struct Atom { + blocker: Option, + version_operator: Option, + category: Category, + name: Name, + version: Option, + slot: Option, + usedeps: Vec, +} diff --git a/src/atom/parsers.rs b/src/atom/parsers.rs new file mode 100644 index 0000000..7b2b096 --- /dev/null +++ b/src/atom/parsers.rs @@ -0,0 +1,227 @@ +use mon::{Parser, r#if, not, numeric1, one_of, opt, tag, take_while}; + +use crate::{ + atom::{ + Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition, + UseDepNegate, UseDepSign, Version, VersionNumber, VersionOperator, VersionSuffix, + VersionSuffixKind, + }, + useflag::parsers::useflag, +}; + +pub fn blocker<'a>() -> impl Parser<&'a str, Output = Blocker> { + 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::Roughly)) +} + +pub fn version_number<'a>() -> impl Parser<&'a str, Output = VersionNumber> { + numeric1().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)) +} + +pub fn version_suffix<'a>() -> impl Parser<&'a str, Output = VersionSuffix> { + version_suffix_kind() + .and(opt(version_number())) + .map(|(kind, number)| VersionSuffix { kind, number }) +} + +pub fn version<'a>() -> impl Parser<&'a str, Output = Version> { + let numbers = version_number().separated_list1(tag(".")); + let suffixes = version_suffix().separated_list(tag("_")); + let rev = version_number().preceded_by(tag("-r")); + + numbers + .and(opt(r#if(|c: &char| c.is_ascii_alphabetic()))) + .and(opt(suffixes.preceded_by(tag("_")))) + .and(opt(rev)) + .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 = take_while(r#if(|c: &char| { + c.is_ascii_alphanumeric() || "+_.-".contains(*c) + })); + + 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 = take_while( + r#if(|c: &char| c.is_ascii_alphanumeric() || "_+".contains(*c)).or(one_of("-".chars()) + .and_not( + version() + .preceded_by(tag("-")) + .followed_by(not(r#if(|c: &char| { + c.is_ascii_alphanumeric() || "_+-".contains(*c) + }))), + )), + ); + + 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)) +} + +pub fn slotname<'a>() -> impl Parser<&'a str, Output = SlotName> { + let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_'); + let rest = take_while(r#if(|c: &char| { + c.is_ascii_alphanumeric() || "+_.-".contains(*c) + })); + + start + .and(rest) + .recognize() + .map(|output: &str| SlotName(output.to_string())) +} + +pub fn slot<'a>() -> impl Parser<&'a str, Output = Slot> { + slotname() + .and(opt(slotname().preceded_by(tag("/")))) + .and(opt(slot_operator())) + .map(|((slot, sub), operator)| Slot { + slot, + sub, + operator, + }) +} + +pub fn usedep_negate<'a>() -> impl Parser<&'a str, Output = UseDepNegate> { + tag("-") + .map(|_| UseDepNegate::Minus) + .or(tag("!").map(|_| UseDepNegate::Exclamation)) +} + +pub fn usedep_sign<'a>() -> impl Parser<&'a str, Output = UseDepSign> { + tag("(-)") + .map(|_| UseDepSign::Disabled) + .or(tag("(+)").map(|_| UseDepSign::Enabled)) +} + +pub fn usedep_condition<'a>() -> impl Parser<&'a str, Output = UseDepCondition> { + tag("=") + .map(|_| UseDepCondition::Eq) + .or(tag("?").map(|_| UseDepCondition::Question)) +} + +pub fn usedep<'a>() -> impl Parser<&'a str, Output = UseDep> { + opt(usedep_negate()) + .and(useflag()) + .and(opt(usedep_sign())) + .and(opt(usedep_condition())) + .map(|(((negate, flag), sign), condition)| UseDep { + negate, + flag, + sign, + condition, + }) +} + +pub fn atom<'a>() -> impl Parser<&'a str, Output = Atom> { + opt(blocker()) + .and(opt(version_operator())) + .and(category()) + .and(name().preceded_by(tag("/"))) + .and(opt(version().preceded_by(tag("-")))) + .and(opt(slot().preceded_by(tag(":")))) + .and(opt(usedep() + .separated_list(tag(",")) + .delimited_by(tag("["), tag("]")))) + .map( + |((((((blocker, version_operator), category), name), version), slot), usedeps)| Atom { + blocker, + version_operator, + category, + name, + version, + slot, + usedeps: usedeps.unwrap_or(Vec::new()), + }, + ) +} + +#[cfg(test)] +mod test { + + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_version() { + let it = InputIter::new("1.0.0v_alpha1_beta1-r1"); + + version().check_finished(it).unwrap(); + } + + #[test] + fn test_name() { + let it = InputIter::new("foo-1-bar-1.0.0"); + + match name().parse(it) { + Ok((_, output)) => { + assert_eq!(output.0.as_str(), "foo-1-bar"); + } + _ => unreachable!(), + } + } + + #[test] + fn test_atom() { + let it = InputIter::new( + "!!>=cat/pkg-1-foo-1.0.0v_alpha1_p20250326-r1:primary/sub=[!a(+),-b(-)=,c?]", + ); + + atom().check_finished(it).unwrap(); + } + + #[test] + fn test_cursed_atom() { + let it = InputIter::new( + "!!>=_.+-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(); + } + + #[test] + fn test_atom_with_star_in_non_empty_slot() { + let it = InputIter::new("foo/bar-1.0.0:*/subslot"); + + assert!(atom().check_finished(it).is_err()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1eb0c3f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +#![deny(clippy::pedantic)] +#![allow(dead_code)] + +pub mod atom; +pub mod useflag; diff --git a/src/useflag/mod.rs b/src/useflag/mod.rs new file mode 100644 index 0000000..3bf929a --- /dev/null +++ b/src/useflag/mod.rs @@ -0,0 +1,10 @@ +pub mod parsers; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UseFlag(String); + +impl UseFlag { + pub fn get(&self) -> &str { + self.0.as_str() + } +} diff --git a/src/useflag/parsers.rs b/src/useflag/parsers.rs new file mode 100644 index 0000000..e6e228b --- /dev/null +++ b/src/useflag/parsers.rs @@ -0,0 +1,10 @@ +use mon::{Parser, alpha1, alphanumeric, one_of, take_while}; + +use crate::useflag::UseFlag; + +pub fn useflag<'a>() -> impl Parser<&'a str, Output = UseFlag> { + alpha1() + .and(alphanumeric().or(take_while(one_of("+_@-".chars())))) + .recognize() + .map(|output: &str| UseFlag(output.to_string())) +}