use crate::term::NamedNode;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Path {
Id,
Pred(NamedNode),
Inverse(Box<Path>),
Seq(Vec<Path>),
Alt(Vec<Path>),
Star(Box<Path>),
}
impl Path {
pub fn seq(parts: Vec<Path>) -> Path {
let mut flat = Vec::with_capacity(parts.len());
for p in parts {
match p {
Path::Id => {}
Path::Seq(inner) => flat.extend(inner),
other => flat.push(other),
}
}
match flat.len() {
0 => Path::Id,
1 => flat.pop().unwrap(),
_ => Path::Seq(flat),
}
}
pub fn alt(parts: Vec<Path>) -> Path {
let mut flat = Vec::with_capacity(parts.len());
for p in parts {
match p {
Path::Alt(inner) => flat.extend(inner),
other => flat.push(other),
}
}
if flat.len() == 1 {
flat.pop().unwrap()
} else {
Path::Alt(flat)
}
}
pub fn inverse(self) -> Path {
match self {
Path::Id => Path::Id,
Path::Inverse(inner) => *inner,
other => Path::Inverse(Box::new(other)),
}
}
pub fn star(self) -> Path {
match self {
Path::Id => Path::Id,
Path::Star(_) => self,
other => Path::Star(Box::new(other)),
}
}
pub fn one_or_more(self) -> Path {
let star = self.clone().star();
Path::seq(vec![self, star])
}
pub fn zero_or_one(self) -> Path {
Path::alt(vec![self, Path::Id])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pred(s: &str) -> Path {
Path::Pred(NamedNode::new(format!("http://ex/{s}")).unwrap())
}
#[test]
fn seq_flattens_and_drops_id() {
let p = Path::seq(vec![
pred("a"),
Path::Id,
Path::seq(vec![pred("b"), pred("c")]),
]);
assert_eq!(p, Path::Seq(vec![pred("a"), pred("b"), pred("c")]));
}
#[test]
fn seq_units() {
assert_eq!(Path::seq(vec![]), Path::Id);
assert_eq!(Path::seq(vec![Path::Id, Path::Id]), Path::Id);
assert_eq!(Path::seq(vec![pred("a")]), pred("a"));
}
#[test]
fn alt_flattens_and_collapses_singleton() {
let p = Path::alt(vec![pred("a"), Path::alt(vec![pred("b"), pred("c")])]);
assert_eq!(p, Path::Alt(vec![pred("a"), pred("b"), pred("c")]));
assert_eq!(Path::alt(vec![pred("a")]), pred("a"));
}
#[test]
fn inverse_is_involution() {
assert_eq!(pred("a").inverse().inverse(), pred("a"));
assert_eq!(Path::Id.inverse(), Path::Id);
}
#[test]
fn star_is_idempotent() {
assert_eq!(pred("a").star().star(), pred("a").star());
assert_eq!(Path::Id.star(), Path::Id);
}
#[test]
fn sugar_expansions() {
assert_eq!(
pred("a").one_or_more(),
Path::Seq(vec![pred("a"), Path::Star(Box::new(pred("a")))])
);
assert_eq!(
pred("a").zero_or_one(),
Path::Alt(vec![pred("a"), Path::Id])
);
}
}