#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, boxed::Box, string::String, vec, vec::Vec};
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum PropertyPath {
Iri(String),
Inverse(Box<PropertyPath>),
Sequence(Vec<PropertyPath>),
Alternative(Vec<PropertyPath>),
ZeroOrMore(Box<PropertyPath>),
OneOrMore(Box<PropertyPath>),
ZeroOrOne(Box<PropertyPath>),
NegatedPropertySet(Vec<PropertyPath>),
}
impl PropertyPath {
pub(crate) fn base_iris(&self) -> Vec<String> {
let mut out = Vec::new();
self.collect_iris(&mut out);
out
}
fn collect_iris(&self, out: &mut Vec<String>) {
match self {
Self::Iri(s) => {
if !s.is_empty() {
out.push(s.clone());
}
}
Self::Inverse(inner) => inner.collect_iris(out),
Self::Sequence(steps) | Self::Alternative(steps) | Self::NegatedPropertySet(steps) => {
for step in steps {
step.collect_iris(out);
}
}
Self::ZeroOrMore(inner) | Self::OneOrMore(inner) | Self::ZeroOrOne(inner) => {
inner.collect_iris(out)
}
}
}
}
struct PathParser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> PathParser<'a> {
fn new(s: &'a str) -> Self {
Self {
input: s.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> u8 {
if self.pos < self.input.len() {
self.input[self.pos]
} else {
0
}
}
fn skip_ws(&mut self) {
while self.pos < self.input.len() && self.input[self.pos].is_ascii_whitespace() {
self.pos += 1;
}
}
fn consume(&mut self) -> u8 {
if self.pos < self.input.len() {
let b = self.input[self.pos];
self.pos += 1;
b
} else {
0
}
}
fn parse_path(&mut self) -> PropertyPath {
self.skip_ws();
self.parse_alt()
}
fn parse_alt(&mut self) -> PropertyPath {
let first = self.parse_seq();
self.skip_ws();
if self.peek() != b'|' {
return first;
}
let mut alts = vec![first];
while self.peek() == b'|' {
self.consume(); self.skip_ws();
alts.push(self.parse_seq());
self.skip_ws();
}
PropertyPath::Alternative(alts)
}
fn parse_seq(&mut self) -> PropertyPath {
let first = self.parse_unary();
self.skip_ws();
if self.peek() != b'/' {
return first;
}
let mut steps = vec![first];
while self.peek() == b'/' {
self.consume(); self.skip_ws();
steps.push(self.parse_unary());
self.skip_ws();
}
PropertyPath::Sequence(steps)
}
fn parse_unary(&mut self) -> PropertyPath {
self.skip_ws();
let has_inverse = self.peek() == b'^';
if has_inverse {
self.consume(); self.skip_ws();
}
let primary = self.parse_primary_with_quantifier();
if has_inverse {
PropertyPath::Inverse(Box::new(primary))
} else {
primary
}
}
fn parse_primary_with_quantifier(&mut self) -> PropertyPath {
let primary = self.parse_primary();
self.skip_ws();
match self.peek() {
b'*' => {
self.consume();
PropertyPath::ZeroOrMore(Box::new(primary))
}
b'+' => {
self.consume();
PropertyPath::OneOrMore(Box::new(primary))
}
b'?' => {
self.consume();
PropertyPath::ZeroOrOne(Box::new(primary))
}
_ => primary,
}
}
fn parse_primary(&mut self) -> PropertyPath {
self.skip_ws();
match self.peek() {
b'(' => {
self.consume(); self.skip_ws();
let inner = self.parse_path();
self.skip_ws();
if self.peek() == b')' {
self.consume();
}
inner
}
b'<' => {
self.consume(); let start = self.pos;
while self.pos < self.input.len() && self.input[self.pos] != b'>' {
self.pos += 1;
}
let iri = core::str::from_utf8(&self.input[start..self.pos])
.unwrap_or("")
.to_owned();
if self.pos < self.input.len() {
self.pos += 1;
} PropertyPath::Iri(iri)
}
b'!' => {
self.consume(); self.skip_ws();
if self.peek() == b'(' {
self.consume(); self.skip_ws();
let mut members = Vec::new();
loop {
self.skip_ws();
if self.peek() == b')' || self.pos >= self.input.len() {
break;
}
let has_inv = self.peek() == b'^';
if has_inv {
self.consume();
self.skip_ws();
}
let p = self.parse_primary();
let p = if has_inv {
PropertyPath::Inverse(Box::new(p))
} else {
p
};
members.push(p);
self.skip_ws();
if self.peek() == b'|' {
self.consume();
} else {
break;
}
}
self.skip_ws();
if self.peek() == b')' {
self.consume();
} PropertyPath::NegatedPropertySet(members)
} else {
let has_inv = self.peek() == b'^';
if has_inv {
self.consume();
self.skip_ws();
}
let p = self.parse_primary();
let p = if has_inv {
PropertyPath::Inverse(Box::new(p))
} else {
p
};
PropertyPath::NegatedPropertySet(vec![p])
}
}
c if c.is_ascii_alphabetic() || c == b'_' => {
let start = self.pos;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b':' || b == b'.'
{
self.pos += 1;
} else {
break;
}
}
let token = core::str::from_utf8(&self.input[start..self.pos])
.unwrap_or("")
.to_owned();
PropertyPath::Iri(token)
}
_ => {
let b = self.peek();
if b != 0 {
self.consume();
}
PropertyPath::Iri(String::new())
}
}
}
}
pub(crate) fn parse_property_path(s: &str) -> PropertyPath {
let trimmed = s.trim();
if trimmed.is_empty() {
return PropertyPath::Iri(String::new());
}
let mut parser = PathParser::new(trimmed);
let result = parser.parse_path();
let remaining = &trimmed[parser.pos..].trim_ascii_start();
if !remaining.is_empty() {
return result;
}
result
}