use crate::xpath::arena::{AstArena, AstNodeId, SourceSpan};
use crate::xpath::ast::AstNode;
use crate::xpath::error::XPathError;
use crate::xpath::lexer::{Lexer, LexerError, Token};
use crate::xpath::{XPathMode, XPathParseOptions};
use std::fmt;
lalrpop_util::lalrpop_mod!(
#[allow(clippy::all)]
#[allow(unused)]
#[allow(dead_code)]
pub xpath_grammar,
"/xpath/parser.rs"
);
#[derive(Debug, Clone)]
pub enum ParseError {
Lexer(LexerError),
Parser {
message: String,
location: Option<usize>,
},
UnexpectedEof,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Lexer(e) => write!(f, "Lexer error: {}", e),
ParseError::Parser { message, location } => {
if let Some(loc) = location {
write!(f, "Parse error at position {}: {}", loc, message)
} else {
write!(f, "Parse error: {}", message)
}
}
ParseError::UnexpectedEof => write!(f, "Unexpected end of input"),
}
}
}
impl std::error::Error for ParseError {}
impl From<LexerError> for ParseError {
fn from(e: LexerError) -> Self {
ParseError::Lexer(e)
}
}
fn map_lalrpop_error(e: lalrpop_util::ParseError<usize, Token, LexerError>) -> ParseError {
match e {
lalrpop_util::ParseError::InvalidToken { location } => ParseError::Parser {
message: "Invalid token".to_string(),
location: Some(location),
},
lalrpop_util::ParseError::UnrecognizedEof { location, expected } => ParseError::Parser {
message: format!("Unexpected end of input, expected one of: {:?}", expected),
location: Some(location),
},
lalrpop_util::ParseError::UnrecognizedToken { token, expected } => ParseError::Parser {
message: format!(
"Unexpected token {:?}, expected one of: {:?}",
token.1, expected
),
location: Some(token.0),
},
lalrpop_util::ParseError::ExtraToken { token } => ParseError::Parser {
message: format!("Extra token: {:?}", token.1),
location: Some(token.0),
},
lalrpop_util::ParseError::User { error } => ParseError::Lexer(error),
}
}
#[derive(Debug)]
pub struct ParsedXPath {
pub arena: AstArena,
pub root: AstNodeId,
pub span: SourceSpan,
}
impl ParsedXPath {
pub fn root_node(&self) -> &AstNode {
self.arena.get(self.root)
}
pub fn get_node(&self, id: AstNodeId) -> &AstNode {
self.arena.get(id)
}
pub fn node_count(&self) -> usize {
self.arena.len()
}
}
pub fn parse(input: &str) -> Result<ParsedXPath, ParseError> {
let mut arena = AstArena::new();
let lexer = Lexer::new(input);
let root = xpath_grammar::ExprParser::new()
.parse(&mut arena, lexer)
.map_err(map_lalrpop_error)?;
Ok(ParsedXPath {
arena,
root,
span: SourceSpan::new(0, input.len()),
})
}
pub fn parse_expr(input: &str, arena: &mut AstArena) -> Result<AstNodeId, ParseError> {
let lexer = Lexer::new(input);
xpath_grammar::ExprParser::new()
.parse(arena, lexer)
.map_err(map_lalrpop_error)
}
pub fn parse_with_mode(input: &str, mode: XPathMode) -> Result<ParsedXPath, ParseError> {
let mut arena = AstArena::new();
let lexer = Lexer::new_with_mode(input, mode);
let root = xpath_grammar::ExprParser::new()
.parse(&mut arena, lexer)
.map_err(map_lalrpop_error)?;
Ok(ParsedXPath {
arena,
root,
span: SourceSpan::new(0, input.len()),
})
}
pub fn parse_expr_with_mode(
input: &str,
mode: XPathMode,
arena: &mut AstArena,
) -> Result<AstNodeId, ParseError> {
let lexer = Lexer::new_with_mode(input, mode);
xpath_grammar::ExprParser::new()
.parse(arena, lexer)
.map_err(map_lalrpop_error)
}
pub fn parse_with_options(
input: &str,
opts: &XPathParseOptions,
) -> Result<ParsedXPath, XPathError> {
let parsed = parse_with_mode(input, opts.mode)?; Ok(parsed)
}
pub fn parse_xpath10(input: &str) -> Result<ParsedXPath, XPathError> {
parse_with_options(
input,
&XPathParseOptions {
mode: XPathMode::XPath10,
},
)
}
pub fn parse_xpath20(input: &str) -> Result<ParsedXPath, XPathError> {
parse_with_options(
input,
&XPathParseOptions {
mode: XPathMode::XPath20,
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xpath::ast::*;
#[test]
fn test_parse_arithmetic() {
let result = parse("1 + 2");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_parse_path() {
let result = parse("/a/b/c");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_parse_variable() {
let result = parse("$x");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_parse_function() {
let result = parse("fn:count(*)");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_lexer_errors() {
let result = parse("'unclosed string");
assert!(result.is_err());
}
#[test]
fn test_parse_xpath10_basic_path() {
let result = parse_with_mode("/a/b", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_parse_xpath10_keyword_as_element_name() {
let result = parse_with_mode("//union", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
}
#[test]
fn test_parse_xpath10_unary_minus() {
let result = parse_with_mode("-a|b", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::UnaryOp(unary) => {
assert_eq!(unary.kind, UnaryOpKind::Negate);
let operand = parsed.get_node(unary.operand);
match operand {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
}
_ => panic!("Expected Union operand, got {:?}", operand),
}
}
_ => panic!("Expected UnaryOp, got {:?}", inner),
}
}
#[test]
fn test_parse_xpath20_unary_minus() {
let result = parse("-a|b");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
let left = parsed.get_node(binop.left);
match left {
AstNode::UnaryOp(unary) => {
assert_eq!(unary.kind, UnaryOpKind::Negate);
}
_ => panic!("Expected UnaryOp on left of Union, got {:?}", left),
}
}
_ => panic!("Expected BinaryOp(Union), got {:?}", inner),
}
}
#[test]
fn test_parse_xpath10_unary_plus() {
let result = parse_with_mode("+a|b", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::UnaryOp(unary) => {
assert_eq!(unary.kind, UnaryOpKind::Identity);
let operand = parsed.get_node(unary.operand);
match operand {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
}
_ => panic!("Expected Union operand, got {:?}", operand),
}
}
_ => panic!("Expected UnaryOp, got {:?}", inner),
}
}
#[test]
fn test_parse_xpath20_unary_plus() {
let result = parse("+a|b");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
let left = parsed.get_node(binop.left);
match left {
AstNode::UnaryOp(unary) => {
assert_eq!(unary.kind, UnaryOpKind::Identity);
}
_ => panic!("Expected UnaryOp on left of Union, got {:?}", left),
}
}
_ => panic!("Expected BinaryOp(Union), got {:?}", inner),
}
}
#[test]
fn test_parse_xpath10_double_unary() {
let result = parse_with_mode("--a|b", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let outer = parsed.get_node(inner_id);
match outer {
AstNode::UnaryOp(unary_outer) => {
assert_eq!(unary_outer.kind, UnaryOpKind::Negate);
let mid = parsed.get_node(unary_outer.operand);
match mid {
AstNode::UnaryOp(unary_inner) => {
assert_eq!(unary_inner.kind, UnaryOpKind::Negate);
let operand = parsed.get_node(unary_inner.operand);
match operand {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
}
_ => panic!("Expected Union operand, got {:?}", operand),
}
}
_ => panic!("Expected inner UnaryOp, got {:?}", mid),
}
}
_ => panic!("Expected outer UnaryOp, got {:?}", outer),
}
}
#[test]
fn test_parse_xpath20_double_unary() {
let result = parse("--a|b");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
let left = parsed.get_node(binop.left);
match left {
AstNode::UnaryOp(unary_outer) => {
assert_eq!(unary_outer.kind, UnaryOpKind::Negate);
let inner_unary = parsed.get_node(unary_outer.operand);
match inner_unary {
AstNode::UnaryOp(unary_inner) => {
assert_eq!(unary_inner.kind, UnaryOpKind::Negate);
}
_ => panic!("Expected inner UnaryOp, got {:?}", inner_unary),
}
}
_ => panic!("Expected UnaryOp on left of Union, got {:?}", left),
}
}
_ => panic!("Expected BinaryOp(Union), got {:?}", inner),
}
}
#[test]
fn test_parse_xpath10_unary_multi_union() {
let result = parse_with_mode("-a|b|c", XPathMode::XPath10);
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::UnaryOp(unary) => {
assert_eq!(unary.kind, UnaryOpKind::Negate);
let operand = parsed.get_node(unary.operand);
match operand {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
}
_ => panic!("Expected Union operand, got {:?}", operand),
}
}
_ => panic!("Expected UnaryOp, got {:?}", inner),
}
}
#[test]
fn test_parse_xpath20_unary_multi_union() {
let result = parse("-a|b|c");
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let parsed = result.unwrap();
let root = parsed.root_node();
let inner_id = match root {
AstNode::Expr(expr) => {
assert_eq!(expr.items.len(), 1);
expr.items[0]
}
_ => panic!("Expected Expr root, got {:?}", root),
};
let inner = parsed.get_node(inner_id);
match inner {
AstNode::BinaryOp(binop) => {
assert_eq!(binop.kind, BinaryOpKind::Union);
fn has_unary_negate(parsed: &ParsedXPath, node_id: AstNodeId) -> bool {
match parsed.get_node(node_id) {
AstNode::UnaryOp(u) => u.kind == UnaryOpKind::Negate,
AstNode::BinaryOp(b) => {
has_unary_negate(parsed, b.left) || has_unary_negate(parsed, b.right)
}
_ => false,
}
}
assert!(
has_unary_negate(&parsed, binop.left),
"Expected UnaryOp(Negate) somewhere on the left side of Union"
);
}
_ => panic!("Expected BinaryOp(Union), got {:?}", inner),
}
}
#[test]
fn test_parse_xpath10_convenience() {
let result = parse_xpath10("/a/b");
assert!(result.is_ok(), "parse_xpath10 failed: {:?}", result.err());
}
#[test]
fn test_parse_xpath20_convenience() {
let result = parse_xpath20("for $x in 1 to 10 return $x");
assert!(result.is_ok(), "parse_xpath20 failed: {:?}", result.err());
}
#[test]
fn test_parse_with_options_xpath10() {
let opts = XPathParseOptions {
mode: XPathMode::XPath10,
};
let result = parse_with_options("//union", &opts);
assert!(
result.is_ok(),
"parse_with_options failed: {:?}",
result.err()
);
}
#[test]
fn test_parse_with_options_returns_xpath_error() {
let result = parse_with_options("'unclosed string", &XPathParseOptions::default());
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.error_code(), Some("XPST0003"));
}
#[test]
fn test_parse_error_to_xpath_error_conversion() {
let parse_err = ParseError::Parser {
message: "test error".to_string(),
location: Some(5),
};
let xpath_err: crate::xpath::error::XPathError = parse_err.into();
assert_eq!(xpath_err.error_code(), Some("XPST0003"));
assert!(xpath_err.to_string().contains("test error"));
}
}