use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use super::ast::{Axis, BinaryOp, Expr, Step};
use super::axes::{
following_union, for_each_on_axis, preceding_union, test_matches,
};
use super::budget::{Budget, sort_factor, string_len};
use super::compare::{compare, ieee_eq};
use super::float;
use super::functions::eval_function;
use super::value::Value;
use super::{EvalContext, EvalError};
use crate::tree::{Document, ExpandedName, NodeId};
pub(crate) struct Ev<'a> {
pub(crate) doc: &'a Document,
pub(crate) vars: &'a EvalContext,
pub(crate) strict: bool,
pub(crate) order: Order<'a>,
pub(crate) budget: Budget,
}
impl Ev<'_> {
pub(crate) fn node_string(
&self,
node: NodeId,
) -> Result<String, EvalError> {
if !self.budget.bounded() {
return Ok(super::value::string_value(self.doc, node));
}
self.budget.charge(self.budget.document_cost)?;
self.budget.string(string_len(self.doc, node))?;
Ok(super::value::string_value(self.doc, node))
}
pub(crate) fn stringify(&self, value: &Value) -> Result<String, EvalError> {
if let Value::NodeSet(nodes) = value {
return nodes
.first()
.map_or_else(|| Ok(String::new()), |&id| self.node_string(id));
}
let bytes = match value {
Value::String(s) => s.len(),
Value::Boolean(_) => 5,
_ => 1024,
};
self.budget.string(bytes)?;
Ok(value.to_str(self.doc))
}
pub(crate) fn number(&self, value: &Value) -> Result<f64, EvalError> {
match value {
Value::NodeSet(_) => {
Ok(super::compare::xpath_number(&self.stringify(value)?))
}
Value::String(s) => {
self.budget.charge(s.len())?;
Ok(value.to_number(self.doc))
}
_ => Ok(value.to_number(self.doc)),
}
}
pub(crate) fn sort(
&self,
nodes: &mut Vec<NodeId>,
) -> Result<(), EvalError> {
self.budget.nodes(nodes.len())?;
self.budget
.charge(nodes.len().saturating_mul(sort_factor(nodes.len())))?;
sort_document_order(&self.order, nodes);
Ok(())
}
fn reserve_axis(&self, axis: Axis, node: NodeId) -> Result<(), EvalError> {
if !self.budget.bounded() {
return Ok(());
}
let units = match axis {
Axis::Child => self.doc.children(node).len(),
Axis::Attribute => self.doc.attribute_nodes(node).len(),
Axis::SelfAxis | Axis::Parent => 1,
Axis::Namespace => {
return self.budget.charge(
self.budget
.document_cost
.saturating_mul(self.doc.nodes.len()),
);
}
_ => return self.budget.charge(self.budget.document_cost),
};
self.budget.charge(
units.saturating_mul(self.budget.name_bytes.saturating_add(1)),
)
}
fn clone_value(&self, value: &Value) -> Result<Value, EvalError> {
self.budget.value(value)?;
match value {
Value::String(s) => self.budget.string(s.len())?,
Value::NodeSet(nodes) => self.check_variable_nodes(nodes)?,
_ => {}
}
Ok(value.clone())
}
fn check_variable_nodes(&self, nodes: &[NodeId]) -> Result<(), EvalError> {
self.budget.charge(nodes.len())?;
if self.budget.bounded()
&& nodes.iter().any(|&id| self.doc.kind(id).is_none())
{
return Err(EvalError::Stale);
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub(crate) struct Ctx {
pub(crate) node: NodeId,
pub(crate) position: usize,
pub(crate) size: usize,
}
pub(crate) fn evaluate(
doc: &Document,
expr: &Expr,
context: NodeId,
vars: &EvalContext,
strict: bool,
) -> Result<Value, EvalError> {
let ev = Ev {
doc,
vars,
strict,
budget: Budget::new(doc, vars.limits)?,
order: Order::for_document(doc),
};
let ctx = Ctx {
node: context,
position: 1,
size: 1,
};
eval(&ev, expr, ctx)
}
pub(crate) fn type_error<T>(
ev: &Ev<'_>,
fallback: T,
message: String,
) -> Result<T, EvalError> {
if ev.strict {
return Err(EvalError::TypeError(message));
}
Ok(fallback)
}
pub(crate) fn eval(
ev: &Ev<'_>,
expr: &Expr,
c: Ctx,
) -> Result<Value, EvalError> {
ev.budget.charge(1)?;
let value = eval_value(ev, expr, c)?;
ev.budget.value(&value)?;
Ok(value)
}
fn eval_value(ev: &Ev<'_>, expr: &Expr, c: Ctx) -> Result<Value, EvalError> {
match expr {
Expr::Literal(s) => literal_value(ev, s),
Expr::Number(n) => Ok(Value::Number(*n)),
Expr::Negate(inner) => negate_value(ev, inner, c),
Expr::Variable(name) => lookup_variable(ev, name),
Expr::Path { absolute, steps } => path_value(ev, *absolute, steps, c),
compound => eval_compound(ev, compound, c),
}
}
fn literal_value(ev: &Ev<'_>, text: &str) -> Result<Value, EvalError> {
ev.budget.string(text.len())?;
Ok(Value::String(String::from(text)))
}
fn negate_value(ev: &Ev<'_>, expr: &Expr, c: Ctx) -> Result<Value, EvalError> {
Ok(Value::Number(-ev.number(&eval(ev, expr, c)?)?))
}
fn path_value(
ev: &Ev<'_>,
absolute: bool,
steps: &[Step],
c: Ctx,
) -> Result<Value, EvalError> {
let start = if absolute { ev.doc.root() } else { c.node };
Ok(Value::NodeSet(eval_steps(ev, steps, alloc::vec![start])?))
}
fn eval_compound(ev: &Ev<'_>, expr: &Expr, c: Ctx) -> Result<Value, EvalError> {
match expr {
Expr::Binary { op, lhs, rhs } => eval_binary(ev, *op, lhs, rhs, c),
Expr::Chain { first, rest } => eval_chain(ev, first, rest, c),
Expr::Function { func, args } => eval_function(ev, *func, args, c),
Expr::Filter(filter) => {
eval_filter(ev, &filter.base, &filter.predicates, &filter.steps, c)
}
_ => unreachable!("not a compound expression"),
}
}
fn lookup_variable(
ev: &Ev<'_>,
name: &ExpandedName,
) -> Result<Value, EvalError> {
let bytes = name
.local
.len()
.saturating_add(name.namespace.as_ref().map_or(0, String::len));
ev.budget.charge(
ev.vars
.binding_count()
.saturating_mul(bytes.saturating_add(1)),
)?;
match ev.vars.variable(name) {
Some(value) => ev.clone_value(value),
None => unbound_variable(ev, name, bytes),
}
}
fn unbound_variable(
ev: &Ev<'_>,
name: &ExpandedName,
bytes: usize,
) -> Result<Value, EvalError> {
ev.budget.string(bytes.saturating_add(2))?;
Err(EvalError::UnboundVariable(match &name.namespace {
Some(uri) => format!("{{{uri}}}{}", name.local),
None => name.local.clone(),
}))
}
fn eval_filter(
ev: &Ev<'_>,
base: &Expr,
predicates: &[Expr],
steps: &[Step],
c: Ctx,
) -> Result<Value, EvalError> {
let mut nodes = match eval(ev, base, c)? {
Value::NodeSet(nodes) => nodes,
other => {
let message = format!(
"a predicate or path step applies to a node-set, not a {}",
other.type_name()
);
return type_error(ev, Value::NodeSet(Vec::new()), message);
}
};
if matches!(base, Expr::Variable(_)) {
ev.sort(&mut nodes)?;
}
let kept = apply_predicates(ev, nodes, predicates)?;
Ok(Value::NodeSet(eval_steps(ev, steps, kept)?))
}
fn eval_steps(
ev: &Ev<'_>,
steps: &[Step],
start: Vec<NodeId>,
) -> Result<Vec<NodeId>, EvalError> {
let mut current = start;
for step in steps {
current = eval_step(ev, step, ¤t)?;
}
Ok(current)
}
fn eval_step(
ev: &Ev<'_>,
step: &Step,
context: &[NodeId],
) -> Result<Vec<NodeId>, EvalError> {
if let [only] = context {
return step_from(ev, step, *only);
}
if let Some(nodes) = eval_axis_union(ev, step, context)? {
return Ok(nodes);
}
let mut seen = Seen::default();
let mut out = Vec::new();
for &node in context {
for found in step_from(ev, step, node)? {
if seen.insert(found) {
out.push(found);
}
}
}
ev.sort(&mut out)?;
Ok(out)
}
fn eval_axis_union(
ev: &Ev<'_>,
step: &Step,
context: &[NodeId],
) -> Result<Option<Vec<NodeId>>, EvalError> {
if !step.predicates.is_empty() {
return Ok(None);
}
match step.axis {
Axis::Following => {
ev.budget.charge(ev.budget.document_cost)?;
Ok(Some(following_union(ev.doc, context, &step.test)))
}
Axis::Preceding => {
ev.budget.charge(ev.budget.document_cost)?;
let mut out = preceding_union(ev.doc, context, &step.test);
ev.sort(&mut out)?;
Ok(Some(out))
}
_ => Ok(None),
}
}
fn step_from(
ev: &Ev<'_>,
step: &Step,
node: NodeId,
) -> Result<Vec<NodeId>, EvalError> {
ev.reserve_axis(step.axis, node)?;
let mut candidates = Vec::new();
for_each_on_axis(ev.doc, node, step.axis, &mut |found| {
if test_matches(ev.doc, found, &step.test, step.axis) {
candidates.push(found);
}
});
let mut kept = apply_predicates(ev, candidates, &step.predicates)?;
if step.axis.is_reverse() {
kept.reverse();
} else if step.axis == Axis::Namespace {
ev.sort(&mut kept)?;
}
Ok(kept)
}
pub(crate) fn apply_predicates(
ev: &Ev<'_>,
mut nodes: Vec<NodeId>,
predicates: &[Expr],
) -> Result<Vec<NodeId>, EvalError> {
for pred in predicates {
nodes = filter_by_predicate(ev, &nodes, pred)?;
}
Ok(nodes)
}
fn filter_by_predicate(
ev: &Ev<'_>,
nodes: &[NodeId],
pred: &Expr,
) -> Result<Vec<NodeId>, EvalError> {
if let Expr::Number(n) = pred {
return Ok(constant_index(nodes, *n));
}
let size = nodes.len();
let mut kept = Vec::new();
for (i, &node) in nodes.iter().enumerate() {
let c = Ctx {
node,
position: i + 1,
size,
};
if predicate_holds(ev, pred, c)? {
kept.push(node);
}
}
Ok(kept)
}
fn predicate_holds(
ev: &Ev<'_>,
pred: &Expr,
c: Ctx,
) -> Result<bool, EvalError> {
Ok(match eval(ev, pred, c)? {
Value::Number(n) => ieee_eq(n, c.position as f64),
other => other.to_boolean(),
})
}
fn constant_index(nodes: &[NodeId], n: f64) -> Vec<NodeId> {
if n < 1.0 || !ieee_eq(n, float::trunc(n)) {
return Vec::new();
}
let position = n as usize;
nodes
.get(position - 1)
.map(|&id| alloc::vec![id])
.unwrap_or_default()
}
#[derive(Default)]
struct Seen {
slots: Vec<bool>,
}
impl Seen {
fn insert(&mut self, id: NodeId) -> bool {
let index = id.index();
if index >= self.slots.len() {
self.slots.resize(index + 1, false);
}
let fresh = !self.slots[index];
self.slots[index] = true;
fresh
}
}
pub(crate) struct Order<'a> {
doc: &'a Document,
local: Option<Vec<u32>>,
}
impl<'a> Order<'a> {
fn for_document(doc: &'a Document) -> Self {
let local = doc.is_order_dirty().then(|| pre_order(doc));
Self { doc, local }
}
fn key(&self, id: NodeId) -> (u32, usize) {
let position = match &self.local {
Some(table) => table.get(id.index()).copied(),
None => self.doc.order_key(id),
};
(position.unwrap_or(u32::MAX), id.index())
}
}
fn pre_order(doc: &Document) -> Vec<u32> {
let mut table = Vec::new();
let mut next = 0_u32;
let mut place = |id: NodeId, table: &mut Vec<u32>| {
let index = id.index();
if index >= table.len() {
table.resize(index + 1, u32::MAX);
}
table[index] = next;
next = next.saturating_add(1);
};
let mut stack = alloc::vec![doc.root()];
while let Some(id) = stack.pop() {
place(id, &mut table);
for &ns in doc.namespace_nodes(id) {
place(ns, &mut table);
}
for &attr in doc.attribute_nodes(id) {
place(attr, &mut table);
}
stack.extend(doc.children(id).iter().rev());
}
table
}
pub(crate) fn sort_document_order(order: &Order<'_>, nodes: &mut Vec<NodeId>) {
nodes.sort_unstable_by_key(|&id| order.key(id));
nodes.dedup();
}
fn eval_binary(
ev: &Ev<'_>,
op: BinaryOp,
lhs: &Expr,
rhs: &Expr,
c: Ctx,
) -> Result<Value, EvalError> {
let l = eval(ev, lhs, c)?;
if let Some(decided) = short_circuit(op, &l) {
return Ok(decided);
}
let r = eval(ev, rhs, c)?;
apply_binary(ev, op, &l, &r)
}
fn eval_chain(
ev: &Ev<'_>,
first: &Expr,
rest: &[(BinaryOp, Expr)],
c: Ctx,
) -> Result<Value, EvalError> {
let mut acc = eval(ev, first, c)?;
for (op, rhs) in rest {
if let Some(decided) = short_circuit(*op, &acc) {
return Ok(decided);
}
let r = eval(ev, rhs, c)?;
acc = apply_binary(ev, *op, &acc, &r)?;
}
Ok(acc)
}
fn short_circuit(op: BinaryOp, l: &Value) -> Option<Value> {
match op {
BinaryOp::And if !l.to_boolean() => Some(Value::Boolean(false)),
BinaryOp::Or if l.to_boolean() => Some(Value::Boolean(true)),
_ => None,
}
}
fn apply_binary(
ev: &Ev<'_>,
op: BinaryOp,
l: &Value,
r: &Value,
) -> Result<Value, EvalError> {
Ok(match op {
BinaryOp::And | BinaryOp::Or => Value::Boolean(r.to_boolean()),
BinaryOp::Union => {
ev.budget.nodes(
l.nodes()
.map_or(0, <[NodeId]>::len)
.saturating_add(r.nodes().map_or(0, <[NodeId]>::len)),
)?;
let mut out = l.nodes().unwrap_or(&[]).to_vec();
out.extend_from_slice(r.nodes().unwrap_or(&[]));
ev.sort(&mut out)?;
Value::NodeSet(out)
}
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Le
| BinaryOp::Gt
| BinaryOp::Ge => {
ev.budget.comparison(l, r)?;
Value::Boolean(compare(ev.doc, op, l, r))
}
BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::Mod => {
Value::Number(arithmetic(op, ev.number(l)?, ev.number(r)?))
}
})
}
fn arithmetic(op: BinaryOp, a: f64, b: f64) -> f64 {
match op {
BinaryOp::Add => a + b,
BinaryOp::Sub => a - b,
BinaryOp::Mul => a * b,
BinaryOp::Div => a / b,
BinaryOp::Mod => a % b,
_ => unreachable!("not an arithmetic operator"),
}
}