use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Name(String),
Star,
Dot,
DotDot,
At,
DoubleColon,
Slash,
DoubleSlash,
Pipe,
Plus,
Minus,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
Multiply,
Or,
And,
Mod,
Div,
Ancestor,
AncestorOrSelf,
Attribute,
Child,
Descendant,
DescendantOrSelf,
Following,
FollowingSibling,
Namespace,
Parent,
Preceding,
PrecedingSibling,
Self_,
StringLiteral(String),
NumberLiteral(f64),
LParen,
RParen,
LBracket,
RBracket,
LBrace, RBrace,
Comma,
Dollar,
Eof,
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Token::Name(n) => write!(f, "{}", n),
Token::Star => write!(f, "*"),
Token::Dot => write!(f, "."),
Token::DotDot => write!(f, ".."),
Token::At => write!(f, "@"),
Token::DoubleColon => write!(f, "::"),
Token::Slash => write!(f, "/"),
Token::DoubleSlash => write!(f, "//"),
Token::Pipe => write!(f, "|"),
Token::Plus => write!(f, "+"),
Token::Minus => write!(f, "-"),
Token::Eq => write!(f, "="),
Token::Ne => write!(f, "!="),
Token::Lt => write!(f, "<"),
Token::Gt => write!(f, ">"),
Token::Le => write!(f, "<="),
Token::Ge => write!(f, ">="),
Token::Multiply => write!(f, "*"),
Token::Or => write!(f, "or"),
Token::And => write!(f, "and"),
Token::Mod => write!(f, "mod"),
Token::Div => write!(f, "div"),
Token::Ancestor => write!(f, "ancestor"),
Token::AncestorOrSelf => write!(f, "ancestor-or-self"),
Token::Attribute => write!(f, "attribute"),
Token::Child => write!(f, "child"),
Token::Descendant => write!(f, "descendant"),
Token::DescendantOrSelf => write!(f, "descendant-or-self"),
Token::Following => write!(f, "following"),
Token::FollowingSibling => write!(f, "following-sibling"),
Token::Namespace => write!(f, "namespace"),
Token::Parent => write!(f, "parent"),
Token::Preceding => write!(f, "preceding"),
Token::PrecedingSibling => write!(f, "preceding-sibling"),
Token::Self_ => write!(f, "self"),
Token::StringLiteral(s) => write!(f, "'{}'", s),
Token::NumberLiteral(n) => write!(f, "{}", n),
Token::LParen => write!(f, "("),
Token::RParen => write!(f, ")"),
Token::LBracket => write!(f, "["),
Token::RBracket => write!(f, "]"),
Token::LBrace => write!(f, "{{"),
Token::RBrace => write!(f, "}}"),
Token::Comma => write!(f, ","),
Token::Dollar => write!(f, "$"),
Token::Eof => write!(f, "<EOF>"),
}
}
}
#[derive(Debug, Clone)]
pub struct Lexer {
input: Vec<u8>,
pos: usize,
ch: u8,
at_start: bool,
}
impl Lexer {
pub fn new(input: &str) -> Self {
let bytes = input.as_bytes().to_vec();
let ch = if bytes.is_empty() { 0 } else { bytes[0] };
Self {
input: bytes,
pos: 0,
ch,
at_start: true,
}
}
fn advance(&mut self) {
self.pos += 1;
self.ch = if self.pos < self.input.len() {
self.input[self.pos]
} else {
0
};
}
fn peek(&self) -> u8 {
if self.pos + 1 < self.input.len() {
self.input[self.pos + 1]
} else {
0
}
}
fn skip_ws(&mut self) {
while self.ch != 0
&& (self.ch == b' ' || self.ch == b'\t' || self.ch == b'\n' || self.ch == b'\r')
{
self.advance();
}
}
fn read_name(&mut self) -> String {
let start = self.pos;
while self.ch != 0
&& (self.ch.is_ascii_alphanumeric()
|| self.ch == b'_'
|| self.ch == b'-'
|| self.ch == b'.')
{
self.advance();
}
String::from_utf8_lossy(&self.input[start..self.pos]).to_string()
}
fn try_keyword_or_axis(&self, name: &str) -> Option<Token> {
match name {
"or" => Some(Token::Or),
"and" => Some(Token::And),
"mod" => Some(Token::Mod),
"div" => Some(Token::Div),
"ancestor" => Some(Token::Ancestor),
"ancestor-or-self" => Some(Token::AncestorOrSelf),
"attribute" => Some(Token::Attribute),
"child" => Some(Token::Child),
"descendant" => Some(Token::Descendant),
"descendant-or-self" => Some(Token::DescendantOrSelf),
"following" => Some(Token::Following),
"following-sibling" => Some(Token::FollowingSibling),
"namespace" => Some(Token::Namespace),
"parent" => Some(Token::Parent),
"preceding" => Some(Token::Preceding),
"preceding-sibling" => Some(Token::PrecedingSibling),
"self" => Some(Token::Self_),
_ => None,
}
}
fn read_number(&mut self) -> f64 {
let start = self.pos;
while self.ch != 0 && self.ch.is_ascii_digit() {
self.advance();
}
if self.ch == b'.' && self.peek().is_ascii_digit() {
self.advance(); while self.ch != 0 && self.ch.is_ascii_digit() {
self.advance();
}
}
let s = String::from_utf8_lossy(&self.input[start..self.pos]).to_string();
s.parse::<f64>().unwrap_or(0.0)
}
fn read_string(&mut self, quote: u8) -> String {
self.advance(); let start = self.pos;
while self.ch != 0 && self.ch != quote {
self.advance();
}
let s = String::from_utf8_lossy(&self.input[start..self.pos]).to_string();
if self.ch == quote {
self.advance(); }
s
}
pub fn next_token(&mut self) -> Token {
self.skip_ws();
if self.ch == 0 {
return Token::Eof;
}
let was_at_start = self.at_start;
self.at_start = false;
match self.ch {
b'(' => {
self.advance();
return Token::LParen;
}
b')' => {
self.advance();
return Token::RParen;
}
b'[' => {
self.advance();
return Token::LBracket;
}
b']' => {
self.advance();
return Token::RBracket;
}
b'{' => {
self.advance();
return Token::LBrace;
}
b'}' => {
self.advance();
return Token::RBrace;
}
b',' => {
self.advance();
return Token::Comma;
}
b'$' => {
self.advance();
return Token::Dollar;
}
b'|' => {
self.advance();
return Token::Pipe;
}
b'+' => {
self.advance();
return Token::Plus;
}
b'@' => {
self.advance();
return Token::At;
}
b'.' => {
if self.peek() == b'.' {
self.advance();
self.advance();
return Token::DotDot;
}
if self.peek().is_ascii_digit() {
return Token::NumberLiteral(self.read_number());
}
self.advance();
return Token::Dot;
}
b'-' => {
self.advance();
return Token::Minus;
}
b'=' => {
self.advance();
return Token::Eq;
}
b'!' => {
if self.peek() == b'=' {
self.advance();
self.advance();
return Token::Ne;
}
self.advance();
return self.next_token();
}
b'<' => {
self.advance();
if self.ch == b'=' {
self.advance();
return Token::Le;
}
return Token::Lt;
}
b'>' => {
self.advance();
if self.ch == b'=' {
self.advance();
return Token::Ge;
}
return Token::Gt;
}
b'/' => {
self.advance();
if self.ch == b'/' {
self.advance();
return Token::DoubleSlash;
}
return Token::Slash;
}
b'*' => {
self.advance();
return Token::Star; }
b':' => {
if self.peek() == b':' {
self.advance();
self.advance();
return Token::DoubleColon;
}
self.advance();
return self.next_token();
}
b'\'' | b'"' => {
let quote = self.ch;
let s = self.read_string(quote);
return Token::StringLiteral(s);
}
_ => {}
}
if self.ch.is_ascii_digit() {
return Token::NumberLiteral(self.read_number());
}
if self.ch.is_ascii_alphabetic() || self.ch == b'_' {
let name = self.read_name();
if self.ch == b':' && self.peek() != b':' {
self.advance(); if self.ch.is_ascii_alphabetic() || self.ch == b'_' || self.ch == b'*' {
if self.ch == b'*' {
self.advance();
let full = format!("{}:*", name);
return Token::Name(full);
}
let local = self.read_name();
return Token::Name(format!("{}:{}", name, local));
}
return Token::Name(name);
}
if self.ch == b':' && self.peek() == b':' {
if let Some(axis) = self.try_keyword_or_axis(&name) {
return axis;
}
return Token::Name(name);
}
if let Some(keyword) = self.try_keyword_or_axis(&name) {
return keyword;
}
return Token::Name(name);
}
self.advance();
self.next_token()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tokenize(s: &str) -> Vec<Token> {
let mut lexer = Lexer::new(s);
let mut tokens = Vec::new();
loop {
let tok = lexer.next_token();
let is_eof = matches!(tok, Token::Eof);
tokens.push(tok);
if is_eof {
break;
}
}
tokens
}
#[test]
fn test_empty() {
let tokens = tokenize("");
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0], Token::Eof);
}
#[test]
fn test_simple_path() {
let tokens = tokenize("child::para");
assert_eq!(
tokens,
vec![
Token::Child,
Token::DoubleColon,
Token::Name("para".into()),
Token::Eof,
]
);
}
#[test]
fn test_absolute_path() {
let tokens = tokenize("/child::para");
assert_eq!(
tokens,
vec![
Token::Slash,
Token::Child,
Token::DoubleColon,
Token::Name("para".into()),
Token::Eof,
]
);
}
#[test]
fn test_short_form() {
let tokens = tokenize("para");
assert_eq!(tokens, vec![Token::Name("para".into()), Token::Eof]);
}
#[test]
fn test_attribute() {
let tokens = tokenize("@attr");
assert_eq!(
tokens,
vec![Token::At, Token::Name("attr".into()), Token::Eof]
);
}
#[test]
fn test_predicate() {
let tokens = tokenize("para[1]");
assert_eq!(
tokens,
vec![
Token::Name("para".into()),
Token::LBracket,
Token::NumberLiteral(1.0),
Token::RBracket,
Token::Eof,
]
);
}
#[test]
fn test_function_call() {
let tokens = tokenize("position()");
assert_eq!(
tokens,
vec![
Token::Name("position".into()),
Token::LParen,
Token::RParen,
Token::Eof,
]
);
}
#[test]
fn test_string_literal() {
let tokens = tokenize("'hello'");
assert_eq!(
tokens,
vec![Token::StringLiteral("hello".into()), Token::Eof]
);
}
#[test]
fn test_number() {
let tokens = tokenize("42");
assert_eq!(tokens, vec![Token::NumberLiteral(42.0), Token::Eof]);
}
#[test]
fn test_decimal() {
let tokens = tokenize("3.14");
assert_eq!(tokens, vec![Token::NumberLiteral(3.14), Token::Eof]);
}
#[test]
fn test_operators() {
let tokens = tokenize("a = b and c != d or e < f");
assert!(tokens.contains(&Token::Eq));
assert!(tokens.contains(&Token::And));
assert!(tokens.contains(&Token::Ne));
assert!(tokens.contains(&Token::Or));
assert!(tokens.contains(&Token::Lt));
}
#[test]
fn test_union() {
let tokens = tokenize("a | b");
assert_eq!(
tokens,
vec![
Token::Name("a".into()),
Token::Pipe,
Token::Name("b".into()),
Token::Eof,
]
);
}
#[test]
fn test_double_slash() {
let tokens = tokenize("//para");
assert_eq!(
tokens,
vec![Token::DoubleSlash, Token::Name("para".into()), Token::Eof]
);
}
#[test]
fn test_qname() {
let tokens = tokenize("xslt:template");
assert_eq!(
tokens,
vec![Token::Name("xslt:template".into()), Token::Eof]
);
}
#[test]
fn test_wildcard() {
let tokens = tokenize("*");
assert_eq!(tokens, vec![Token::Star, Token::Eof]);
}
#[test]
fn test_ns_wildcard() {
let tokens = tokenize("ns:*");
assert_eq!(tokens, vec![Token::Name("ns:*".into()), Token::Eof]);
}
#[test]
fn test_dot_dot() {
let tokens = tokenize("..");
assert_eq!(tokens, vec![Token::DotDot, Token::Eof]);
}
#[test]
fn test_axis_keyword() {
let tokens = tokenize("ancestor-or-self::node()");
assert_eq!(
tokens,
vec![
Token::AncestorOrSelf,
Token::DoubleColon,
Token::Name("node".into()),
Token::LParen,
Token::RParen,
Token::Eof,
]
);
}
#[test]
fn test_complex_expression() {
let tokens = tokenize("/html/body//div[@class='main']/p[1]");
let names: Vec<String> = tokens
.iter()
.filter_map(|t| match t {
Token::Name(n) => Some(n.clone()),
Token::Div => Some("div".to_string()),
Token::Mod => Some("mod".to_string()),
Token::And => Some("and".to_string()),
Token::Or => Some("or".to_string()),
_ => None,
})
.collect();
assert_eq!(names, vec!["html", "body", "div", "class", "p"]);
}
#[test]
fn test_variable() {
let tokens = tokenize("$var");
assert_eq!(
tokens,
vec![Token::Dollar, Token::Name("var".into()), Token::Eof]
);
}
}