impl atom parsing

This commit is contained in:
John Turner
2025-10-23 00:52:35 -04:00
parent 6e4b45027e
commit 2e7d8cfbb9
5 changed files with 358 additions and 0 deletions

106
src/atom/mod.rs Normal file
View File

@@ -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<VersionNumber>,
}
#[derive(Clone, Debug, Get)]
pub struct Version {
numbers: Vec<VersionNumber>,
letter: Option<char>,
suffixes: Vec<VersionSuffix>,
rev: Option<VersionNumber>,
}
#[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<SlotName>,
operator: Option<SlotOperator>,
}
#[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<UseDepNegate>,
flag: UseFlag,
sign: Option<UseDepSign>,
condition: Option<UseDepCondition>,
}
#[derive(Clone, Debug, Get)]
pub struct Atom {
blocker: Option<Blocker>,
version_operator: Option<VersionOperator>,
category: Category,
name: Name,
version: Option<Version>,
slot: Option<Slot>,
usedeps: Vec<UseDep>,
}

227
src/atom/parsers.rs Normal file
View File

@@ -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());
}
}