use std::fmt;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Expr {
Binary(BinaryOp, Box<Expr>, Box<Expr>),
Negate(Box<Expr>),
Path(Box<PathExpr>),
Literal(String),
Number(f64),
Variable(NameTest),
Function {
name: String,
args: Vec<Expr>,
},
Sequence(Vec<Expr>),
Range(Box<Expr>, Box<Expr>),
For {
variable: NameTest,
input: Box<Expr>,
body: Box<Expr>,
},
Quantified {
quantifier: Quantifier,
variable: NameTest,
input: Box<Expr>,
test: Box<Expr>,
},
If {
condition: Box<Expr>,
then_branch: Box<Expr>,
else_branch: Box<Expr>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Quantifier {
Some,
Every,
}
impl Quantifier {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Quantifier::Some => "some",
Quantifier::Every => "every",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BinaryOp {
Or,
And,
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
Add,
Subtract,
Multiply,
Divide,
Modulo,
Union,
ValueEqual,
ValueNotEqual,
ValueLess,
ValueLessEqual,
ValueGreater,
ValueGreaterEqual,
NodeIs,
NodeBefore,
NodeAfter,
}
impl BinaryOp {
#[must_use]
pub const fn is_node_comparison(self) -> bool {
matches!(
self,
BinaryOp::NodeIs | BinaryOp::NodeBefore | BinaryOp::NodeAfter
)
}
#[must_use]
pub const fn is_value_comparison(self) -> bool {
matches!(
self,
BinaryOp::ValueEqual
| BinaryOp::ValueNotEqual
| BinaryOp::ValueLess
| BinaryOp::ValueLessEqual
| BinaryOp::ValueGreater
| BinaryOp::ValueGreaterEqual
)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
BinaryOp::Or => "or",
BinaryOp::And => "and",
BinaryOp::Equal => "=",
BinaryOp::NotEqual => "!=",
BinaryOp::Less => "<",
BinaryOp::LessEqual => "<=",
BinaryOp::Greater => ">",
BinaryOp::GreaterEqual => ">=",
BinaryOp::Add => "+",
BinaryOp::Subtract => "-",
BinaryOp::Multiply => "*",
BinaryOp::Divide => "div",
BinaryOp::Modulo => "mod",
BinaryOp::Union => "|",
BinaryOp::ValueEqual => "eq",
BinaryOp::ValueNotEqual => "ne",
BinaryOp::ValueLess => "lt",
BinaryOp::ValueLessEqual => "le",
BinaryOp::ValueGreater => "gt",
BinaryOp::ValueGreaterEqual => "ge",
BinaryOp::NodeIs => "is",
BinaryOp::NodeBefore => "<<",
BinaryOp::NodeAfter => ">>",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathExpr {
pub start: PathStart,
pub steps: Vec<Step>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathStart {
Root,
Context,
Expr(Box<Expr>, Vec<Expr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Step {
pub axis: Axis,
pub node_test: NodeTest,
pub predicates: Vec<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Axis {
Ancestor,
AncestorOrSelf,
Attribute,
Child,
Descendant,
DescendantOrSelf,
Following,
FollowingSibling,
Namespace,
Parent,
Preceding,
PrecedingSibling,
SelfAxis,
}
impl Axis {
#[must_use]
pub const fn is_reverse(self) -> bool {
matches!(
self,
Axis::Ancestor
| Axis::AncestorOrSelf
| Axis::Preceding
| Axis::PrecedingSibling
| Axis::Parent
)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Axis::Ancestor => "ancestor",
Axis::AncestorOrSelf => "ancestor-or-self",
Axis::Attribute => "attribute",
Axis::Child => "child",
Axis::Descendant => "descendant",
Axis::DescendantOrSelf => "descendant-or-self",
Axis::Following => "following",
Axis::FollowingSibling => "following-sibling",
Axis::Namespace => "namespace",
Axis::Parent => "parent",
Axis::Preceding => "preceding",
Axis::PrecedingSibling => "preceding-sibling",
Axis::SelfAxis => "self",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Axis> {
Some(match name {
"ancestor" => Axis::Ancestor,
"ancestor-or-self" => Axis::AncestorOrSelf,
"attribute" => Axis::Attribute,
"child" => Axis::Child,
"descendant" => Axis::Descendant,
"descendant-or-self" => Axis::DescendantOrSelf,
"following" => Axis::Following,
"following-sibling" => Axis::FollowingSibling,
"namespace" => Axis::Namespace,
"parent" => Axis::Parent,
"preceding" => Axis::Preceding,
"preceding-sibling" => Axis::PrecedingSibling,
"self" => Axis::SelfAxis,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameTest {
pub prefix: Option<String>,
pub local: String,
}
impl NameTest {
#[must_use]
pub fn parse(raw: &str) -> NameTest {
match raw.split_once(':') {
Some((prefix, local)) => NameTest {
prefix: Some(prefix.to_string()),
local: local.to_string(),
},
None => NameTest {
prefix: None,
local: raw.to_string(),
},
}
}
}
impl fmt::Display for NameTest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.prefix {
Some(p) => write!(f, "{p}:{}", self.local),
None => f.write_str(&self.local),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum NodeTest {
Name(NameTest),
Wildcard,
NamespaceWildcard(String),
AnyNode,
Text,
Comment,
ProcessingInstruction(Option<String>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reverse_axes_are_marked() {
assert!(Axis::Ancestor.is_reverse());
assert!(Axis::PrecedingSibling.is_reverse());
assert!(!Axis::Child.is_reverse());
assert!(!Axis::Following.is_reverse());
}
#[test]
fn axis_names_round_trip() {
for axis in [Axis::Ancestor, Axis::DescendantOrSelf, Axis::SelfAxis] {
assert_eq!(Axis::from_name(axis.as_str()), Some(axis));
}
assert_eq!(Axis::from_name("nonsense"), None);
}
#[test]
fn name_test_splits_prefix() {
let n = NameTest::parse("p:a");
assert_eq!(n.prefix.as_deref(), Some("p"));
assert_eq!(n.local, "a");
assert_eq!(NameTest::parse("a").prefix, None);
}
}