//! `xpath` on XML and XMLList values: XPath 1.0 expressions
//! () evaluated on E4X trees.
//!
//! An expression is read into tokens, then into a syntax tree whose nodes live in one arena, and is
//! evaluated with an explicit task stack, so neither step uses native recursion. Brackets nest at
//! most [`crate::Options::max_depth`] deep. Every token, task and visited node takes fuel, and the
//! token, tree, node-set and string buffers are held against the heap limit while they live.
//!
//! `/` is a root node above the topmost node of the context node's tree. A node-set result is
//! returned as an XMLList of the selected nodes themselves, in document order with attributes
//! before children. The root node and namespace nodes can be selected and tested inside an
//! expression, but a result that holds one throws a `TypeError`, since E4X has no such value. The
//! tree keeps no DTD, so `id()` selects nothing. Prefixes in the expression are read from the
//! namespaces in scope on the context element, and `xml` is always bound.
use std::collections::{HashMap, HashSet};
use std::mem::replace;
use std::rc::Rc;
use crate::builtins::arg;
use crate::error::TOO_DEEP;
use crate::heap::{Key, ObjId, XmlKind, XmlName, XmlNs, key};
use crate::interp::{Abrupt, ErrorType, HeldBytes, Interp, JsResult};
use crate::regexp_vm::use_fuel_n;
use crate::string_builtins::{self, charge_units, flat_charged, push_held};
use crate::value::Value;
use crate::xml::{Xv, is_name_part, is_name_start, is_xml_space, valid_xml_units};
use crate::xml_builtins::scope_of;
const XML_URI: &str = "http://www.w3.org/XML/1998/namespace";
const COLON: u16 = b':' as u16;
const STAR: u16 = b'*' as u16;
const SLASH: u16 = b'/' as u16;
const DOT: u16 = b'.' as u16;
const EQUALS: u16 = b'=' as u16;
const DASH: u16 = b'-' as u16;
/// The estimated bytes of a syntax tree node's own lists, beyond its slot in the arena.
const EXPR_BYTES: usize = 64;
/// The estimated bytes of one entry of a hash map or set the evaluator keeps.
const ENTRY_BYTES: usize = 64;
fn empty() -> Key {
Rc::from(Vec::new())
}
fn syntax(interp: &mut Interp, message: &str) -> Abrupt {
interp.throw(ErrorType::Syntax, message)
}
fn internal() -> Abrupt {
Abrupt::Internal("XPath evaluation state".into())
}
fn is_digit(c: u16) -> bool {
(0x30..=0x39).contains(&c)
}
// ---- tokens ----
#[derive(Clone, Copy, PartialEq)]
enum Punct {
LParen,
RParen,
LBracket,
RBracket,
Dot,
DotDot,
At,
Comma,
ColonColon,
Slash,
SlashSlash,
Pipe,
Plus,
Minus,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
/// A token as the lexer reads it, before names and `*` are told apart by their position.
enum Raw {
Punct(Punct),
Star,
Name(Option, Key),
PrefixStar(Key),
Literal(Key),
Number(f64),
Var(Key),
}
#[derive(Clone, Copy, PartialEq)]
enum Axis {
Ancestor,
AncestorOrSelf,
Attribute,
Child,
Descendant,
DescendantOrSelf,
Following,
FollowingSibling,
Namespace,
Parent,
Preceding,
PrecedingSibling,
Itself,
}
const AXES: [(&str, Axis); 13] = [
("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::Itself),
];
impl Axis {
/// Whether the axis lists its nodes in reverse document order.
fn reverse(self) -> bool {
matches!(self, Axis::Ancestor | Axis::AncestorOrSelf | Axis::Preceding | Axis::PrecedingSibling)
}
}
/// A node test. A prefix is resolved when the expression runs.
#[derive(Clone)]
enum Test {
Any,
Prefix(Key),
Name(Option, Key),
Node,
Text,
Comment,
Pi(Option),
}
#[derive(Clone, Copy, PartialEq)]
enum Bin {
Or,
And,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Add,
Sub,
Mul,
Div,
Mod,
Union,
}
impl Bin {
fn precedence(self) -> u8 {
match self {
Bin::Or => 1,
Bin::And => 2,
Bin::Eq | Bin::Ne => 3,
Bin::Lt | Bin::Le | Bin::Gt | Bin::Ge => 4,
Bin::Add | Bin::Sub => 5,
Bin::Mul | Bin::Div | Bin::Mod => 6,
Bin::Union => 8,
}
}
}
/// The precedence of unary minus: tighter than `*`, looser than `|`.
const NEG_PRECEDENCE: u8 = 7;
#[derive(Clone, Copy, PartialEq)]
enum Func {
Last,
Position,
Count,
Id,
LocalName,
NamespaceUri,
Name,
String,
Concat,
StartsWith,
Contains,
SubstringBefore,
SubstringAfter,
Substring,
StringLength,
NormalizeSpace,
Translate,
Boolean,
Not,
True,
False,
Lang,
Number,
Sum,
Floor,
Ceiling,
Round,
}
/// The core function library (XPath 1.0 section 4): each name, and its least and most arguments.
const FUNCS: [(&str, Func, usize, usize); 27] = [
("last", Func::Last, 0, 0),
("position", Func::Position, 0, 0),
("count", Func::Count, 1, 1),
("id", Func::Id, 1, 1),
("local-name", Func::LocalName, 0, 1),
("namespace-uri", Func::NamespaceUri, 0, 1),
("name", Func::Name, 0, 1),
("string", Func::String, 0, 1),
("concat", Func::Concat, 2, usize::MAX),
("starts-with", Func::StartsWith, 2, 2),
("contains", Func::Contains, 2, 2),
("substring-before", Func::SubstringBefore, 2, 2),
("substring-after", Func::SubstringAfter, 2, 2),
("substring", Func::Substring, 2, 3),
("string-length", Func::StringLength, 0, 1),
("normalize-space", Func::NormalizeSpace, 0, 1),
("translate", Func::Translate, 3, 3),
("boolean", Func::Boolean, 1, 1),
("not", Func::Not, 1, 1),
("true", Func::True, 0, 0),
("false", Func::False, 0, 0),
("lang", Func::Lang, 1, 1),
("number", Func::Number, 0, 1),
("sum", Func::Sum, 1, 1),
("floor", Func::Floor, 1, 1),
("ceiling", Func::Ceiling, 1, 1),
("round", Func::Round, 1, 1),
];
/// A token the parser reads: names and `*` with their roles decided (XPath 1.0 section 3.7).
enum Tok {
Punct(Punct),
Op(Bin),
Test(Test),
Axis(Axis),
Func(Func, &'static str),
Literal(Key),
Number(f64),
Var(Key),
}
fn ncname_end(src: &[u16], at: usize) -> Option {
let first = *src.get(at)?;
if first == COLON || !is_name_start(first) {
return None;
}
let mut end = at + 1;
while src.get(end).is_some_and(|&c| c != COLON && is_name_part(c)) {
end += 1;
}
valid_xml_units(src.get(at..end)?).then_some(end)
}
fn lex_name(src: &[u16], at: usize) -> Option<(Raw, usize)> {
let end = ncname_end(src, at)?;
let first: Key = Rc::from(src.get(at..end)?);
if src.get(end) != Some(&COLON) || src.get(end + 1) == Some(&COLON) {
return Some((Raw::Name(None, first), end));
}
if src.get(end + 1) == Some(&STAR) {
return Some((Raw::PrefixStar(first), end + 2));
}
let local_end = ncname_end(src, end + 1)?;
Some((Raw::Name(Some(first), Rc::from(src.get(end + 1..local_end)?)), local_end))
}
fn lex_number(src: &[u16], at: usize) -> Option<(Raw, usize)> {
let mut end = at;
while src.get(end).copied().is_some_and(is_digit) {
end += 1;
}
if src.get(end) == Some(&DOT) {
end += 1;
while src.get(end).copied().is_some_and(is_digit) {
end += 1;
}
}
let text = String::from_utf16(src.get(at..end)?).ok()?;
Some((Raw::Number(text.parse().ok()?), end))
}
fn lex_literal(src: &[u16], at: usize, quote: u16) -> Option<(Raw, usize)> {
let rest = src.get(at + 1..)?;
let len = rest.iter().position(|&c| c == quote)?;
Some((Raw::Literal(Rc::from(rest.get(..len)?)), at + len + 2))
}
fn lex_var(src: &[u16], at: usize) -> Option<(Raw, usize)> {
let (Raw::Name(..), end) = lex_name(src, at + 1)? else { return None };
Some((Raw::Var(Rc::from(src.get(at + 1..end)?)), end))
}
/// The token that starts at `src[at]`, and the offset after it.
fn lex_one(src: &[u16], at: usize, c: u16) -> Option<(Raw, usize)> {
if c != COLON && is_name_start(c) {
return lex_name(src, at);
}
let next = src.get(at + 1).copied();
let p = |p: Punct, n: usize| Some((Raw::Punct(p), at + n));
match char::from_u32(u32::from(c))? {
'(' => p(Punct::LParen, 1),
')' => p(Punct::RParen, 1),
'[' => p(Punct::LBracket, 1),
']' => p(Punct::RBracket, 1),
'@' => p(Punct::At, 1),
',' => p(Punct::Comma, 1),
'|' => p(Punct::Pipe, 1),
'+' => p(Punct::Plus, 1),
'-' => p(Punct::Minus, 1),
'=' => p(Punct::Eq, 1),
'*' => Some((Raw::Star, at + 1)),
'/' if next == Some(SLASH) => p(Punct::SlashSlash, 2),
'/' => p(Punct::Slash, 1),
':' if next == Some(COLON) => p(Punct::ColonColon, 2),
'!' if next == Some(EQUALS) => p(Punct::Ne, 2),
'<' if next == Some(EQUALS) => p(Punct::Le, 2),
'<' => p(Punct::Lt, 1),
'>' if next == Some(EQUALS) => p(Punct::Ge, 2),
'>' => p(Punct::Gt, 1),
'.' if next == Some(DOT) => p(Punct::DotDot, 2),
'.' if next.is_some_and(is_digit) => lex_number(src, at),
'.' => p(Punct::Dot, 1),
'"' | '\'' => lex_literal(src, at, c),
'$' => lex_var(src, at),
_ if is_digit(c) => lex_number(src, at),
_ => None,
}
}
fn lex(interp: &mut Interp, held: &mut HeldBytes, src: &[u16]) -> JsResult> {
let mut out = Vec::new();
let mut at = 0;
loop {
while src.get(at).copied().is_some_and(is_xml_space) {
at += 1;
}
let Some(&c) = src.get(at) else { return Ok(out) };
let Some((raw, end)) = lex_one(src, at, c) else {
return Err(syntax(interp, &format!("the XPath expression cannot be read at offset {at}")));
};
charge_units(interp, end - at)?;
push_held(interp, held, &mut out, raw)?;
at = end;
}
}
fn units_are(units: &[u16], text: &str) -> bool {
units.iter().copied().eq(text.encode_utf16())
}
fn operator_name(name: &[u16]) -> Option {
[("and", Bin::And), ("or", Bin::Or), ("mod", Bin::Mod), ("div", Bin::Div)].into_iter().find(|(n, _)| units_are(name, n)).map(|(_, b)| b)
}
fn punct_token(p: Punct) -> Tok {
match p {
Punct::Pipe => Tok::Op(Bin::Union),
Punct::Plus => Tok::Op(Bin::Add),
Punct::Minus => Tok::Op(Bin::Sub),
Punct::Eq => Tok::Op(Bin::Eq),
Punct::Ne => Tok::Op(Bin::Ne),
Punct::Lt => Tok::Op(Bin::Lt),
Punct::Le => Tok::Op(Bin::Le),
Punct::Gt => Tok::Op(Bin::Gt),
Punct::Ge => Tok::Op(Bin::Ge),
other => Tok::Punct(other),
}
}
/// Whether a token at this point is an operator: there is a token before it, and it is not `@`,
/// `::`, `(`, `[`, `,` or an operator (XPath 1.0 section 3.7).
fn operator_position(last: Option<&Tok>) -> bool {
last.is_some_and(|t| {
!matches!(
t,
Tok::Punct(Punct::LParen | Punct::LBracket | Punct::At | Punct::Comma | Punct::Slash | Punct::SlashSlash)
| Tok::Op(_)
| Tok::Axis(_)
| Tok::Func(..)
)
})
}
/// A name followed by `(`: a node type test, which also takes its `)`, or a function call.
/// Returns the token and how many raw tokens it takes.
fn call_or_node_type(interp: &mut Interp, prefix: Option<&Key>, name: &Key, after: &[Raw]) -> JsResult<(Tok, usize)> {
if prefix.is_none() {
let plain = [("node", Test::Node), ("text", Test::Text), ("comment", Test::Comment)].into_iter().find(|(n, _)| units_are(name, n));
if let Some((_, test)) = plain {
return match after.first() {
Some(Raw::Punct(Punct::RParen)) => Ok((Tok::Test(test), 3)),
_ => Err(syntax(interp, "an XPath node type test takes no argument")),
};
}
if units_are(name, "processing-instruction") {
return match after {
[Raw::Punct(Punct::RParen), ..] => Ok((Tok::Test(Test::Pi(None)), 3)),
[Raw::Literal(target), Raw::Punct(Punct::RParen), ..] => Ok((Tok::Test(Test::Pi(Some(target.clone()))), 4)),
_ => Err(syntax(interp, "processing-instruction() takes one string literal or none")),
};
}
}
let found = FUNCS.iter().find(|(n, ..)| prefix.is_none() && units_are(name, n));
match found {
Some(&(n, f, ..)) => Ok((Tok::Func(f, n), 2)),
None => {
let shown = String::from_utf16_lossy(name);
Err(interp.type_error(&format!("{shown} is not an XPath 1.0 function")))
}
}
}
fn classify(interp: &mut Interp, held: &mut HeldBytes, raw: &[Raw]) -> JsResult> {
let mut out: Vec = Vec::new();
let mut at = 0;
while let Some(r) = raw.get(at) {
interp.use_fuel()?;
let operator = operator_position(out.last());
let next = raw.get(at + 1);
let (tok, used) = match r {
Raw::Star if operator => (Tok::Op(Bin::Mul), 1),
Raw::Star => (Tok::Test(Test::Any), 1),
Raw::Name(None, n) if operator => match operator_name(n) {
Some(op) => (Tok::Op(op), 1),
None => return Err(syntax(interp, "an XPath operator is missing")),
},
Raw::Name(p, n) if matches!(next, Some(Raw::Punct(Punct::LParen))) => {
call_or_node_type(interp, p.as_ref(), n, raw.get(at + 2..).unwrap_or_default())?
}
Raw::Name(p, n) if matches!(next, Some(Raw::Punct(Punct::ColonColon))) => {
let axis = AXES.iter().find(|(a, _)| p.is_none() && units_are(n, a)).map(|&(_, a)| a);
match axis {
Some(a) => (Tok::Axis(a), 2),
None => return Err(syntax(interp, &format!("{} is not an XPath axis", String::from_utf16_lossy(n)))),
}
}
Raw::Name(p, n) => (Tok::Test(Test::Name(p.clone(), n.clone())), 1),
Raw::PrefixStar(p) => (Tok::Test(Test::Prefix(p.clone())), 1),
Raw::Punct(Punct::ColonColon) => return Err(syntax(interp, "an XPath axis name is missing before ::")),
Raw::Punct(p) => (punct_token(*p), 1),
Raw::Literal(s) => (Tok::Literal(s.clone()), 1),
Raw::Number(n) => (Tok::Number(*n), 1),
Raw::Var(v) => (Tok::Var(v.clone()), 1),
};
push_held(interp, held, &mut out, tok)?;
at += used;
}
Ok(out)
}
// ---- the syntax tree ----
struct Step {
axis: Axis,
test: Test,
preds: Vec,
}
enum Expr {
Bin(Bin, usize, usize),
Neg(usize),
Num(f64),
Lit(Key),
Var(usize),
Call(Func, Vec),
/// A location path, or a filter expression with its predicates, followed by steps.
Path {
filter: Option<(usize, Vec)>,
absolute: bool,
steps: Vec,
},
}
/// A parsed expression: the tree in its arena, its root, and the variables and prefixes it names.
struct Parsed {
exprs: Vec,
root: usize,
vars: Vec,
prefixes: Vec,
_held: HeldBytes,
}
#[derive(Clone, Copy)]
enum Op {
Bin(Bin),
Neg,
}
impl Op {
fn precedence(self) -> u8 {
match self {
Op::Bin(b) => b.precedence(),
Op::Neg => NEG_PRECEDENCE,
}
}
}
/// A path expression being read.
#[derive(Default)]
struct Operand {
filter: Option<(usize, Vec)>,
absolute: bool,
steps: Vec,
/// The last step is `.` or `..`, which take no predicate.
abbreviated: bool,
}
impl Operand {
fn bare_root(&self) -> bool {
self.absolute && self.steps.is_empty() && self.filter.is_none()
}
}
enum FrameKind {
Top,
Group,
Pred,
Args(Func, &'static str, Vec),
}
/// One bracket level: its operators and operands, read by operator precedence.
struct Frame {
kind: FrameKind,
ops: Vec,
vals: Vec,
operand: Option,
}
impl Frame {
fn new(kind: FrameKind) -> Self {
Frame { kind, ops: Vec::new(), vals: Vec::new(), operand: None }
}
}
#[derive(Clone, Copy, PartialEq)]
enum Want {
Operand,
Step,
Test(Axis),
After,
}
struct Parser<'i> {
interp: &'i mut Interp,
held: HeldBytes,
exprs: Vec,
vars: Vec,
var_index: HashMap,
prefixes: HashSet,
frames: Vec,
want: Want,
}
impl Parser<'_> {
fn error(&mut self, message: &str) -> Abrupt {
syntax(self.interp, message)
}
fn push_expr(&mut self, e: Expr) -> JsResult {
self.held.add(self.interp, EXPR_BYTES)?;
push_held(self.interp, &mut self.held, &mut self.exprs, e)?;
Ok(self.exprs.len() - 1)
}
fn top(&mut self) -> JsResult<&mut Frame> {
self.frames.last_mut().ok_or_else(internal)
}
fn operand(&mut self) -> JsResult<&mut Operand> {
self.top()?.operand.as_mut().ok_or_else(internal)
}
fn open(&mut self, kind: FrameKind) -> JsResult<()> {
if self.frames.len() >= self.interp.options.max_depth {
return Err(self.error(TOO_DEEP));
}
self.held.add(self.interp, size_of::())?;
self.frames.push(Frame::new(kind));
self.want = Want::Operand;
Ok(())
}
fn token(&mut self, tok: Tok) -> JsResult<()> {
match self.want {
Want::Operand => self.operand_token(tok),
Want::Step => self.step_token(tok),
Want::Test(axis) => match tok {
Tok::Test(test) => self.push_step(axis, test, false),
_ => Err(self.error("an XPath node test is missing")),
},
Want::After => self.after_token(tok),
}
}
fn primary(&mut self, e: Expr) -> JsResult<()> {
let id = self.push_expr(e)?;
self.top()?.operand = Some(Operand { filter: Some((id, Vec::new())), ..Operand::default() });
self.want = Want::After;
Ok(())
}
fn operand_token(&mut self, tok: Tok) -> JsResult<()> {
match tok {
Tok::Op(Bin::Sub) => {
self.top()?.ops.push(Op::Neg);
Ok(())
}
Tok::Punct(Punct::Slash) => {
self.top()?.operand = Some(Operand { absolute: true, ..Operand::default() });
self.want = Want::Step;
Ok(())
}
Tok::Punct(Punct::SlashSlash) => {
self.top()?.operand = Some(Operand { absolute: true, ..Operand::default() });
self.descendants_step()
}
Tok::Literal(s) => self.primary(Expr::Lit(s)),
Tok::Number(n) => self.primary(Expr::Num(n)),
Tok::Var(name) => {
let next = self.vars.len();
let at = *self.var_index.entry(name.clone()).or_insert(next);
if at == next {
push_held(self.interp, &mut self.held, &mut self.vars, name)?;
}
self.primary(Expr::Var(at))
}
Tok::Func(f, n) => self.open(FrameKind::Args(f, n, Vec::new())),
Tok::Punct(Punct::LParen) => self.open(FrameKind::Group),
Tok::Punct(Punct::RParen) if self.empty_call()? => self.close_paren(),
tok @ (Tok::Test(_) | Tok::Axis(_) | Tok::Punct(Punct::At | Punct::Dot | Punct::DotDot)) => {
self.top()?.operand = Some(Operand::default());
self.step_token(tok)
}
_ => Err(self.error("an XPath operand is missing")),
}
}
/// Whether the open bracket is a function call's, with nothing in it yet.
fn empty_call(&mut self) -> JsResult {
let f = self.top()?;
Ok(matches!(&f.kind, FrameKind::Args(_, _, args) if args.is_empty()) && f.ops.is_empty() && f.vals.is_empty() && f.operand.is_none())
}
fn descendants_step(&mut self) -> JsResult<()> {
self.operand()?.steps.push(Step { axis: Axis::DescendantOrSelf, test: Test::Node, preds: Vec::new() });
self.want = Want::Step;
Ok(())
}
fn push_step(&mut self, axis: Axis, test: Test, abbreviated: bool) -> JsResult<()> {
if let Test::Prefix(p) | Test::Name(Some(p), _) = &test
&& !self.prefixes.contains(p)
{
self.held.add(self.interp, ENTRY_BYTES)?;
self.prefixes.insert(p.clone());
}
self.held.add(self.interp, size_of::())?;
let operand = self.operand()?;
operand.steps.push(Step { axis, test, preds: Vec::new() });
operand.abbreviated = abbreviated;
self.want = Want::After;
Ok(())
}
fn step_token(&mut self, tok: Tok) -> JsResult<()> {
match tok {
Tok::Punct(Punct::Dot) => self.push_step(Axis::Itself, Test::Node, true),
Tok::Punct(Punct::DotDot) => self.push_step(Axis::Parent, Test::Node, true),
Tok::Punct(Punct::At) => {
self.want = Want::Test(Axis::Attribute);
Ok(())
}
Tok::Axis(a) => {
self.want = Want::Test(a);
Ok(())
}
Tok::Test(t) => self.push_step(Axis::Child, t, false),
tok if self.operand()?.bare_root() => {
self.want = Want::After;
self.after_token(tok)
}
_ => Err(self.error("an XPath step is missing")),
}
}
fn after_token(&mut self, tok: Tok) -> JsResult<()> {
match tok {
Tok::Punct(p @ (Punct::LBracket | Punct::Slash | Punct::SlashSlash)) => {
let operand = self.operand()?;
let (bare, abbreviated) = (operand.bare_root(), operand.abbreviated);
if bare || (p == Punct::LBracket && abbreviated) {
return Err(self.error("an XPath predicate or step cannot follow here"));
}
match p {
Punct::LBracket => self.open(FrameKind::Pred),
Punct::Slash => {
self.want = Want::Step;
Ok(())
}
_ => self.descendants_step(),
}
}
Tok::Op(b) => self.binary(b),
Tok::Punct(Punct::RParen) => self.close_paren(),
Tok::Punct(Punct::RBracket) => self.close_bracket(),
Tok::Punct(Punct::Comma) => self.next_argument(),
_ => Err(self.error("an XPath operator is missing")),
}
}
fn finish_operand(&mut self, operand: Operand) -> JsResult {
match operand {
Operand { filter: Some((e, preds)), steps, .. } if preds.is_empty() && steps.is_empty() => Ok(e),
Operand { filter, absolute, steps, .. } => self.push_expr(Expr::Path { filter, absolute, steps }),
}
}
fn reduce_one(&mut self, frame: &mut Frame) -> JsResult<()> {
let op = frame.ops.pop().ok_or_else(internal)?;
let rhs = frame.vals.pop().ok_or_else(internal)?;
let e = match op {
Op::Neg => Expr::Neg(rhs),
Op::Bin(b) => Expr::Bin(b, frame.vals.pop().ok_or_else(internal)?, rhs),
};
let id = self.push_expr(e)?;
frame.vals.push(id);
Ok(())
}
fn binary(&mut self, b: Bin) -> JsResult<()> {
let mut frame = self.frames.pop().ok_or_else(internal)?;
let result = self.binary_in(&mut frame, b);
self.frames.push(frame);
result?;
self.want = Want::Operand;
Ok(())
}
fn binary_in(&mut self, frame: &mut Frame, b: Bin) -> JsResult<()> {
let operand = frame.operand.take().ok_or_else(internal)?;
let e = self.finish_operand(operand)?;
frame.vals.push(e);
while frame.ops.last().is_some_and(|op| op.precedence() >= b.precedence()) {
self.interp.use_fuel()?;
self.reduce_one(frame)?;
}
self.held.add(self.interp, 2 * size_of::())?;
frame.ops.push(Op::Bin(b));
Ok(())
}
/// Ends the innermost bracket and returns its kind and expression.
fn close(&mut self) -> JsResult<(FrameKind, usize)> {
if self.frames.len() < 2 {
return Err(self.error("the XPath expression has an unmatched closing bracket"));
}
let mut frame = self.frames.pop().ok_or_else(internal)?;
let e = self.reduce(&mut frame)?;
Ok((frame.kind, e))
}
fn reduce(&mut self, frame: &mut Frame) -> JsResult {
let operand = frame.operand.take().ok_or_else(|| syntax(self.interp, "an XPath operand is missing"))?;
let e = self.finish_operand(operand)?;
frame.vals.push(e);
while !frame.ops.is_empty() {
self.interp.use_fuel()?;
self.reduce_one(frame)?;
}
match frame.vals.as_slice() {
[e] => Ok(*e),
_ => Err(internal()),
}
}
fn close_paren(&mut self) -> JsResult<()> {
let (kind, e) = if self.top()?.operand.is_none() {
(self.frames.pop().ok_or_else(internal)?.kind, None)
} else {
let (kind, e) = self.close()?;
(kind, Some(e))
};
match kind {
FrameKind::Group => self.primary_id(e.ok_or_else(internal)?),
FrameKind::Args(f, name, mut args) => {
args.extend(e);
let (_, _, least, most) = FUNCS.iter().find(|(_, g, ..)| *g == f).copied().ok_or_else(internal)?;
if args.len() < least || args.len() > most {
return Err(self.interp.type_error(&format!("the XPath function {name} takes the wrong number of arguments")));
}
let call = self.push_expr(Expr::Call(f, args))?;
self.primary_id(call)
}
_ => Err(self.error("the XPath expression has an unmatched )")),
}
}
fn primary_id(&mut self, id: usize) -> JsResult<()> {
if self.frames.is_empty() {
return Err(self.error("the XPath expression has an unmatched )"));
}
self.top()?.operand = Some(Operand { filter: Some((id, Vec::new())), ..Operand::default() });
self.want = Want::After;
Ok(())
}
fn close_bracket(&mut self) -> JsResult<()> {
let (kind, e) = self.close()?;
if !matches!(kind, FrameKind::Pred) {
return Err(self.error("the XPath expression has an unmatched ]"));
}
let operand = self.operand()?;
match (operand.steps.last_mut(), operand.filter.as_mut()) {
(Some(step), _) => step.preds.push(e),
(None, Some((_, preds))) => preds.push(e),
(None, None) => return Err(internal()),
}
self.want = Want::After;
Ok(())
}
fn next_argument(&mut self) -> JsResult<()> {
if !matches!(self.top()?.kind, FrameKind::Args(..)) || self.frames.len() < 2 {
return Err(self.error("a comma in an XPath expression must separate function arguments"));
}
let mut frame = self.frames.pop().ok_or_else(internal)?;
let e = self.reduce(&mut frame);
if let (Ok(e), FrameKind::Args(_, _, args)) = (&e, &mut frame.kind) {
args.push(*e);
}
frame.ops.clear();
frame.vals.clear();
self.frames.push(frame);
e?;
self.want = Want::Operand;
Ok(())
}
fn finish(mut self) -> JsResult {
let complete = self.want == Want::After || (self.want == Want::Step && self.operand()?.bare_root());
if !complete || self.frames.len() != 1 {
return Err(self.error("the XPath expression ends early"));
}
let mut frame = self.frames.pop().ok_or_else(internal)?;
let root = self.reduce(&mut frame)?;
Ok(Parsed { exprs: self.exprs, root, vars: self.vars, prefixes: self.prefixes.into_iter().collect(), _held: self.held })
}
}
fn parse(interp: &mut Interp, src: &[u16]) -> JsResult {
let mut held = HeldBytes::new(interp);
let raw = lex(interp, &mut held, src)?;
let toks = classify(interp, &mut held, &raw)?;
drop(raw);
let mut p = Parser {
interp,
held,
exprs: Vec::new(),
vars: Vec::new(),
var_index: HashMap::new(),
prefixes: HashSet::new(),
frames: vec![Frame::new(FrameKind::Top)],
want: Want::Operand,
};
for tok in toks {
p.interp.use_fuel()?;
p.token(tok)?;
}
p.finish()
}
// ---- values ----
/// A node of the XPath data model. `Root` is the root above the topmost node it names, and `Ns`
/// is a namespace node of `owner`, at position `at` among its namespaces.
#[derive(Clone, PartialEq, Eq, Hash)]
enum XNode {
Root(ObjId),
Node(ObjId),
Ns { owner: ObjId, at: u32, prefix: Key, uri: Key },
}
#[derive(Clone, Copy, PartialEq)]
enum NodeKind {
Root,
Ns,
Xml(XmlKind),
}
/// A list of nodes whose buffer is held against the heap limit.
struct Nodes {
list: Vec,
held: HeldBytes,
}
impl Nodes {
fn new(interp: &Interp) -> Self {
Nodes { list: Vec::new(), held: HeldBytes::new(interp) }
}
fn push(&mut self, interp: &mut Interp, n: XNode) -> JsResult<()> {
push_held(interp, &mut self.held, &mut self.list, n)
}
}
/// A string value whose units are held against the heap limit when the evaluator built them.
struct Text {
units: Key,
_held: HeldBytes,
}
impl Text {
fn plain(interp: &Interp, units: Key) -> Self {
Text { units, _held: HeldBytes::new(interp) }
}
}
struct TextBuf {
units: Vec,
held: HeldBytes,
}
impl TextBuf {
fn new(interp: &Interp) -> Self {
TextBuf { units: Vec::new(), held: HeldBytes::new(interp) }
}
fn push(&mut self, interp: &mut Interp, more: &[u16]) -> JsResult<()> {
charge_units(interp, more.len())?;
let want = self.units.len().saturating_add(more.len());
let cap = self.units.capacity();
if want > cap {
let grown = want.max(cap.saturating_mul(2)).max(16);
self.held.add(interp, (grown - cap).saturating_mul(2))?;
self.units.reserve_exact(grown - self.units.len());
}
self.units.extend_from_slice(more);
Ok(())
}
fn finish(self) -> Text {
Text { units: Rc::from(self.units), _held: self.held }
}
}
enum Val {
Set(Nodes),
Bool(bool),
Num(f64),
Str(Text),
}
/// A variable's value, read from the variables object before evaluation starts.
enum Bound {
Set(Vec),
Bool(bool),
Num(f64),
Str(Key),
}
#[derive(Clone)]
struct Ctx {
node: XNode,
pos: usize,
size: usize,
}
/// A location path being evaluated. Stage 0 applies the filter expression's predicates to the
/// filter's nodes; stage `s` applies step `s - 1` to each node of `inputs` in turn.
struct PathRun {
path: usize,
stage: usize,
inputs: Nodes,
ci: usize,
cur: Nodes,
pred: usize,
at: usize,
kept: Nodes,
waiting: bool,
out: Nodes,
seen: HashSet,
seen_held: HeldBytes,
}
enum Task {
Eval(usize, Ctx),
Bin(Bin),
Neg,
Logic { and: bool, rhs: usize, ctx: Ctx },
Bool,
Call(Func, usize, Ctx),
PathStart(usize),
Path(Box),
}
fn to_bool(v: &Val) -> bool {
match v {
Val::Set(s) => !s.list.is_empty(),
Val::Bool(b) => *b,
Val::Num(n) => *n != 0.0 && !n.is_nan(),
Val::Str(t) => !t.units.is_empty(),
}
}
/// The number a string stands for (XPath 1.0 section 4.4): optional white space, an optional
/// minus, digits with an optional fraction, optional white space; anything else is NaN.
fn str_num(interp: &mut Interp, units: &[u16]) -> JsResult {
let start = units.iter().position(|&c| !is_xml_space(c)).unwrap_or(units.len());
let end = units.iter().rposition(|&c| !is_xml_space(c)).map_or(start, |e| e + 1);
let body = units.get(start..end).unwrap_or_default();
let (negative, digits) = match body.split_first() {
Some((&DASH, rest)) => (true, rest),
_ => (false, body),
};
let dots = digits.iter().filter(|&&c| c == DOT).count();
if dots > 1 || !digits.iter().all(|&c| c == DOT || is_digit(c)) || !digits.iter().any(|&c| is_digit(c)) {
return Ok(f64::NAN);
}
let mut held = HeldBytes::new(interp);
held.add(interp, digits.len())?;
let text: String = digits.iter().map(|&c| char::from(c as u8)).collect();
let n: f64 = text.parse().unwrap_or(f64::NAN);
Ok(if negative { -n } else { n })
}
/// A number as a string (XPath 1.0 section 4.2): integers without a decimal point, and no
/// exponent.
fn num_text(n: f64) -> String {
if n.is_nan() {
"NaN".into()
} else if n.is_infinite() {
if n > 0.0 { "Infinity".into() } else { "-Infinity".into() }
} else if n == 0.0 {
"0".into()
} else {
format!("{n}")
}
}
/// `round` (XPath 1.0 section 4.4): the nearest integer, halves toward positive infinity, with
/// negative zero for values from -0.5 up to zero.
fn round(n: f64) -> f64 {
if !n.is_finite() || n == 0.0 {
return n;
}
let floor = n.floor();
let r = if n - floor >= 0.5 { floor + 1.0 } else { floor };
if r == 0.0 && n < 0.0 { -0.0 } else { r }
}
fn compare_numbers(op: Bin, a: f64, b: f64) -> bool {
match op {
Bin::Eq => a == b,
Bin::Ne => a != b,
Bin::Lt => a < b,
Bin::Le => a <= b,
Bin::Gt => a > b,
_ => a >= b,
}
}
fn flip(op: Bin) -> Bin {
match op {
Bin::Lt => Bin::Gt,
Bin::Le => Bin::Ge,
Bin::Gt => Bin::Lt,
Bin::Ge => Bin::Le,
other => other,
}
}
fn code_points(units: &[u16]) -> Vec {
char::decode_utf16(units.iter().copied()).map(|r| r.map_or_else(|e| u32::from(e.unpaired_surrogate()), u32::from)).collect()
}
fn push_point(out: &mut Vec, p: u32) {
match char::from_u32(p) {
Some(c) => {
let mut buf = [0u16; 2];
out.extend_from_slice(c.encode_utf16(&mut buf));
}
None => out.extend(u16::try_from(p).ok()),
}
}
fn lower(c: u16) -> u16 {
if (0x41..=0x5A).contains(&c) { c + 0x20 } else { c }
}
fn same_ignoring_case(a: &[u16], b: &[u16]) -> bool {
a.len() == b.len() && a.iter().zip(b).all(|(&x, &y)| lower(x) == lower(y))
}
fn name_uri(name: &XmlName) -> &[u16] {
name.uri.as_deref().unwrap_or_default()
}
fn kind_of(interp: &Interp, n: &XNode) -> JsResult {
Ok(match n {
XNode::Root(_) => NodeKind::Root,
XNode::Ns { .. } => NodeKind::Ns,
XNode::Node(x) => NodeKind::Xml(interp.xml_kind(*x)?),
})
}
fn parent_of(interp: &Interp, n: &XNode) -> JsResult