//! E4X syntax (ECMA-357 sections 11.1 and 12.1): literals, names and the operators on them. //! //! A literal is read by scanning the source units with an explicit stack of open tags, so tag //! nesting uses no native recursion and is held to [`crate::Options::max_depth`]. Only an embedded //! `{expression}` re-enters the expression parser, and that recursion is counted as any other. use super::{Parser, Result, describe}; use crate::ast::{Expr, ExprId, Stmt, StmtId, XmlNamePart, XmlPart, XmlQualifier}; use crate::error::TOO_DEEP; use crate::token::{Keyword, Punct, Span, TokenKind}; use crate::xml::{is_name_start, is_xml_space, scan_name, scan_reference}; const LT: u16 = b'<' as u16; const GT: u16 = b'>' as u16; const SLASH: u16 = b'/' as u16; const AMP: u16 = b'&' as u16; const LBRACE: u16 = b'{' as u16; const EQ: u16 = b'=' as u16; const DQUOTE: u16 = b'"' as u16; const SQUOTE: u16 = b'\'' as u16; /// A literal being read: the markup since the last embedded expression, and the finished parts. #[derive(Default)] struct Scan { text: Vec, parts: Vec, } impl Scan { fn flush(&mut self) { if !self.text.is_empty() { self.parts.push(XmlPart::Text(std::mem::take(&mut self.text))); } } } impl Parser<'_> { pub(super) fn at_default_xml_namespace(&self) -> bool { self.e4x && self.peek().is_some_and(|t| t.ident() == Some("xml")) } /// `default xml namespace = expression;`, from `default`. pub(super) fn parse_default_xml_namespace(&mut self, start: Span) -> Result { self.expect_keyword(Keyword::Default)?; self.bump()?; if self.tok.ident() != Some("namespace") { return Err(self.error_at_token(&self.tok, format!("expected 'namespace' but found {}", describe(&self.tok)))); } self.bump()?; self.expect(Punct::Eq)?; let value = self.parse_expression(false)?; self.semicolon()?; Ok(self.ast.push_stmt(Stmt::DefaultXmlNamespace(value), self.span_from(start))) } /// After a `.`: the next token starts an E4X name or a filter rather than a property name. pub(super) fn at_e4x_member(&self) -> bool { match &self.tok.kind { TokenKind::Punct(Punct::At | Punct::Star | Punct::LParen) => true, _ => self.tok.identifier_name().is_some() && self.peek().is_some_and(|t| t.is_punct(Punct::ColonColon)), } } /// `object.@name`, `object.*`, `object.ns::name` or `object.(predicate)`, from the token after /// the `.`. pub(super) fn parse_xml_member(&mut self, object: ExprId) -> Result { let start = self.ast.expr_span(object); if self.at(Punct::LParen) { self.bump()?; let predicate = self.parse_expression(false)?; self.expect(Punct::RParen)?; return Ok(self.ast.push_expr(Expr::XmlFilter { object, predicate }, self.span_from(start))); } let index = self.parse_xml_name()?; Ok(self.ast.push_expr(Expr::Index { object, index }, self.span_from(start))) } /// `object..name`, from the `..`. pub(super) fn parse_xml_descendants(&mut self, object: ExprId) -> Result { let start = self.ast.expr_span(object); self.bump()?; let name = self.parse_xml_name()?; Ok(self.ast.push_expr(Expr::XmlDescendants { object, name }, self.span_from(start))) } /// `@name`, `*` or `@*` standing alone as a primary expression. pub(super) fn parse_xml_scope_name(&mut self) -> Result { let start = self.tok.span; let name = self.parse_xml_name()?; Ok(self.ast.push_expr(Expr::XmlScopeName(name), self.span_from(start))) } /// `qualifier::name` as a primary expression, from the `::`; `qualifier` is already read. pub(super) fn parse_xml_qualified_primary(&mut self, qualifier: ExprId) -> Result { let start = self.ast.expr_span(qualifier); self.bump()?; let name = self.parse_xml_name_part()?; let qualifier = Some(XmlQualifier::Expr(qualifier)); let id = self.ast.push_expr(Expr::XmlName { attribute: false, qualifier, name }, self.span_from(start)); Ok(self.ast.push_expr(Expr::XmlScopeName(id), self.span_from(start))) } /// An E4X name: `@`, then `*`, a name or `[expression]`, with an optional `qualifier::`. fn parse_xml_name(&mut self) -> Result { let start = self.tok.span; let attribute = self.eat(Punct::At)?; let first_is_star = self.at(Punct::Star); let qualifier_ident = self.tok.identifier_name().map(str::to_string).filter(|_| matches!(self.tok.kind, TokenKind::Ident(_))); let first = self.parse_xml_name_part()?; let (qualifier, name) = if self.at(Punct::ColonColon) { let qualifier = if first_is_star { XmlQualifier::Any } else if let Some(name) = qualifier_ident { let span = self.span_from(start); XmlQualifier::Expr(self.ast.push_expr(Expr::Ident(name), span)) } else { return Err(self.error_at_token(&self.tok, "expected a namespace name before '::'")); }; self.bump()?; (Some(qualifier), self.parse_xml_name_part()?) } else { (None, first) }; Ok(self.ast.push_expr(Expr::XmlName { attribute, qualifier, name }, self.span_from(start))) } fn parse_xml_name_part(&mut self) -> Result { if self.eat(Punct::Star)? { return Ok(XmlNamePart::Star); } if self.eat(Punct::LBracket)? { let e = self.parse_expression(false)?; self.expect(Punct::RBracket)?; return Ok(XmlNamePart::Computed(e)); } let Some(name) = self.tok.identifier_name().map(str::to_string) else { return Err(self.error_at_token(&self.tok, format!("expected an XML name but found {}", describe(&self.tok)))); }; self.bump()?; Ok(XmlNamePart::Ident(name)) } /// An XML or XMLList literal, from its `<` (the current token). pub(super) fn parse_xml_literal(&mut self) -> Result { let start = self.tok.span; let token_slot = self.tokens.as_ref().map(Vec::len); if let Some(tokens) = &mut self.tokens { tokens.push(self.tok.clone()); } let mut pos = start.start as usize; let list = self.unit(pos + 1) == Some(GT); if list { pos += 2; } let mut scan = Scan::default(); // The name of each open element; `None` when it is computed. let mut open: Vec>> = Vec::new(); loop { if !list && open.is_empty() && !scan.text.is_empty() | !scan.parts.is_empty() { break; } let Some(c) = self.unit(pos) else { return Err(self.xml_error(pos, "unterminated XML literal")) }; if c == LT { if self.unit(pos + 1) == Some(SLASH) { if open.is_empty() { if list && self.unit(pos + 2) == Some(GT) { pos += 3; break; } return Err(self.xml_error(pos, "a closing tag has no matching open tag")); } pos = self.xml_close_tag(pos, &mut open, &mut scan)?; } else if self.starts_with(pos, "