#[derive(Debug, Clone)]
pub enum Expr {
Number(f64),
String(String),
Variable(String),
BinaryOp {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
},
UnaryNeg(Box<Expr>),
FunctionCall {
name: String,
args: Vec<Expr>,
},
Path {
steps: Vec<Step>,
},
RootPath {
steps: Vec<Step>,
},
Filter {
expr: Box<Expr>,
predicates: Vec<Expr>,
},
Union(Box<Expr>, Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
And,
Or,
}
#[derive(Debug, Clone)]
pub struct Step {
pub axis: Axis,
pub node_test: NodeTest,
pub predicates: Vec<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Axis {
Child,
Descendant,
Parent,
Ancestor,
FollowingSibling,
PrecedingSibling,
Following,
Preceding,
Attribute,
Namespace,
Self_,
DescendantOrSelf,
AncestorOrSelf,
}
impl Axis {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Child => "child",
Self::Descendant => "descendant",
Self::Parent => "parent",
Self::Ancestor => "ancestor",
Self::FollowingSibling => "following-sibling",
Self::PrecedingSibling => "preceding-sibling",
Self::Following => "following",
Self::Preceding => "preceding",
Self::Attribute => "attribute",
Self::Namespace => "namespace",
Self::Self_ => "self",
Self::DescendantOrSelf => "descendant-or-self",
Self::AncestorOrSelf => "ancestor-or-self",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"child" => Some(Self::Child),
"descendant" => Some(Self::Descendant),
"parent" => Some(Self::Parent),
"ancestor" => Some(Self::Ancestor),
"following-sibling" => Some(Self::FollowingSibling),
"preceding-sibling" => Some(Self::PrecedingSibling),
"following" => Some(Self::Following),
"preceding" => Some(Self::Preceding),
"attribute" => Some(Self::Attribute),
"namespace" => Some(Self::Namespace),
"self" => Some(Self::Self_),
"descendant-or-self" => Some(Self::DescendantOrSelf),
"ancestor-or-self" => Some(Self::AncestorOrSelf),
_ => None,
}
}
}
impl std::fmt::Display for Axis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeTest {
Name(String),
Wildcard,
PrefixWildcard(String),
Node,
Text,
Comment,
ProcessingInstruction(Option<String>),
}
impl std::fmt::Display for NodeTest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Name(name) => write!(f, "{name}"),
Self::Wildcard => f.write_str("*"),
Self::PrefixWildcard(prefix) => write!(f, "{prefix}:*"),
Self::Node => f.write_str("node()"),
Self::Text => f.write_str("text()"),
Self::Comment => f.write_str("comment()"),
Self::ProcessingInstruction(None) => f.write_str("processing-instruction()"),
Self::ProcessingInstruction(Some(name)) => {
write!(f, "processing-instruction('{name}')")
}
}
}
}
impl std::fmt::Display for BinaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Add => f.write_str("+"),
Self::Sub => f.write_str("-"),
Self::Mul => f.write_str("*"),
Self::Div => f.write_str("div"),
Self::Mod => f.write_str("mod"),
Self::Eq => f.write_str("="),
Self::Neq => f.write_str("!="),
Self::Lt => f.write_str("<"),
Self::Lte => f.write_str("<="),
Self::Gt => f.write_str(">"),
Self::Gte => f.write_str(">="),
Self::And => f.write_str("and"),
Self::Or => f.write_str("or"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_axis_roundtrip() {
let axes = [
Axis::Child,
Axis::Descendant,
Axis::Parent,
Axis::Ancestor,
Axis::FollowingSibling,
Axis::PrecedingSibling,
Axis::Following,
Axis::Preceding,
Axis::Attribute,
Axis::Namespace,
Axis::Self_,
Axis::DescendantOrSelf,
Axis::AncestorOrSelf,
];
for axis in axes {
let name = axis.as_str();
let parsed = Axis::parse(name);
assert_eq!(parsed, Some(axis), "roundtrip failed for {name}");
}
}
#[test]
fn test_axis_from_str_invalid() {
assert_eq!(Axis::parse("invalid"), None);
assert_eq!(Axis::parse(""), None);
assert_eq!(Axis::parse("children"), None);
}
#[test]
fn test_axis_display() {
assert_eq!(Axis::Child.to_string(), "child");
assert_eq!(Axis::DescendantOrSelf.to_string(), "descendant-or-self");
assert_eq!(Axis::AncestorOrSelf.to_string(), "ancestor-or-self");
assert_eq!(Axis::FollowingSibling.to_string(), "following-sibling");
}
#[test]
fn test_node_test_display() {
assert_eq!(NodeTest::Name("foo".to_string()).to_string(), "foo");
assert_eq!(NodeTest::Wildcard.to_string(), "*");
assert_eq!(
NodeTest::PrefixWildcard("svg".to_string()).to_string(),
"svg:*"
);
assert_eq!(NodeTest::Node.to_string(), "node()");
assert_eq!(NodeTest::Text.to_string(), "text()");
assert_eq!(NodeTest::Comment.to_string(), "comment()");
assert_eq!(
NodeTest::ProcessingInstruction(None).to_string(),
"processing-instruction()"
);
assert_eq!(
NodeTest::ProcessingInstruction(Some("xml-stylesheet".to_string())).to_string(),
"processing-instruction('xml-stylesheet')"
);
}
#[test]
fn test_binary_op_display() {
assert_eq!(BinaryOp::Add.to_string(), "+");
assert_eq!(BinaryOp::Sub.to_string(), "-");
assert_eq!(BinaryOp::Mul.to_string(), "*");
assert_eq!(BinaryOp::Div.to_string(), "div");
assert_eq!(BinaryOp::Mod.to_string(), "mod");
assert_eq!(BinaryOp::Eq.to_string(), "=");
assert_eq!(BinaryOp::Neq.to_string(), "!=");
assert_eq!(BinaryOp::Lt.to_string(), "<");
assert_eq!(BinaryOp::Lte.to_string(), "<=");
assert_eq!(BinaryOp::Gt.to_string(), ">");
assert_eq!(BinaryOp::Gte.to_string(), ">=");
assert_eq!(BinaryOp::And.to_string(), "and");
assert_eq!(BinaryOp::Or.to_string(), "or");
}
#[test]
fn test_expr_clone() {
let expr = Expr::BinaryOp {
op: BinaryOp::Add,
left: Box::new(Expr::Number(1.0)),
right: Box::new(Expr::Number(2.0)),
};
let cloned = expr.clone();
match cloned {
Expr::BinaryOp { op, .. } => assert_eq!(op, BinaryOp::Add),
_ => panic!("unexpected variant after clone"),
}
}
#[test]
fn test_step_construction() {
let step = Step {
axis: Axis::Child,
node_test: NodeTest::Name("p".to_string()),
predicates: vec![Expr::Number(1.0)],
};
assert_eq!(step.axis, Axis::Child);
assert_eq!(step.node_test, NodeTest::Name("p".to_string()));
assert_eq!(step.predicates.len(), 1);
}
}