/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Lexical analyzer for XPath expressions */ #include "txExprLexer.h" #include "nsGkAtoms.h" #include "nsString.h" #include "nsError.h" #include "txXMLUtils.h" /** * Creates a new ExprLexer */ txExprLexer::txExprLexer() : mPosition(nullptr), mCurrentItem(nullptr), mFirstItem(nullptr), mLastItem(nullptr), mTokenCount(0) {} /** * Destroys this instance of an txExprLexer */ txExprLexer::~txExprLexer() { //-- delete tokens Token* tok = mFirstItem; while (tok) { Token* temp = tok->mNext; delete tok; tok = temp; } mCurrentItem = nullptr; } Token* txExprLexer::nextToken() { if (!mCurrentItem) { MOZ_ASSERT_UNREACHABLE("nextToken called on uninitialized lexer"); return nullptr; } if (mCurrentItem->mType == Token::END) { // Do not progress beyond the end token return mCurrentItem; } Token* token = mCurrentItem; mCurrentItem = mCurrentItem->mNext; return token; } void txExprLexer::addToken(Token* aToken) { if (mLastItem) { mLastItem->mNext = aToken; } if (!mFirstItem) { mFirstItem = aToken; mCurrentItem = aToken; } mLastItem = aToken; ++mTokenCount; } /** * Returns true if the following Token should be an operator. * This is a helper for the first bullet of [XPath 3.7] * Lexical Structure */ bool txExprLexer::nextIsOperatorToken(Token* aToken) { if (!aToken || aToken->mType == Token::NULL_TOKEN) { return false; } /* This relies on the tokens having the right order in txExprLexer.h */ return aToken->mType < Token::COMMA || aToken->mType > Token::UNION_OP; } /** * Parses the given string into a sequence of Tokens */ nsresult txExprLexer::parse(const nsAString& aPattern) { iterator end; aPattern.BeginReading(mPosition); aPattern.EndReading(end); //-- initialize previous token, this will automatically get //-- deleted when it goes out of scope Token nullToken(nullptr, nullptr, Token::NULL_TOKEN); Token::Type defType; Token* newToken = nullptr; Token* prevToken = &nullToken; bool isToken; while (mPosition < end) { defType = Token::CNAME; isToken = true; if (*mPosition == DOLLAR_SIGN) { if (++mPosition == end || !XMLUtils::isLetter(*mPosition)) { return NS_ERROR_XPATH_INVALID_VAR_NAME; } defType = Token::VAR_REFERENCE; } // just reuse the QName parsing, which will use defType // the token to construct if (XMLUtils::isLetter(*mPosition)) { // NCName, can get QName or OperatorName; // FunctionName, NodeName, and AxisSpecifier may want whitespace, // and are dealt with below iterator start = mPosition; while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) { /* just go */ } if (mPosition < end && *mPosition == COLON) { // try QName or wildcard, might need to step back for axis if (++mPosition == end) { return NS_ERROR_XPATH_UNEXPECTED_END; } if (XMLUtils::isLetter(*mPosition)) { while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) { /* just go */ } } else if (*mPosition == '*' && defType != Token::VAR_REFERENCE) { // eat wildcard for NameTest, bail for var ref at COLON ++mPosition; } else { --mPosition; // step back } } if (nextIsOperatorToken(prevToken)) { nsDependentSubstring op(Substring(start, mPosition)); if (nsGkAtoms::_and->Equals(op)) { defType = Token::AND_OP; } else if (nsGkAtoms::_or->Equals(op)) { defType = Token::OR_OP; } else if (nsGkAtoms::mod->Equals(op)) { defType = Token::MODULUS_OP; } else if (nsGkAtoms::div->Equals(op)) { defType = Token::DIVIDE_OP; } else { // XXX QUESTION: spec is not too precise // badops is sure an error, but is bad:ops, too? We say yes! return NS_ERROR_XPATH_OPERATOR_EXPECTED; } } newToken = new Token(start, mPosition, defType); } else if (isXPathDigit(*mPosition)) { iterator start = mPosition; while (++mPosition < end && isXPathDigit(*mPosition)) { /* just go */ } if (mPosition < end && *mPosition == '.') { while (++mPosition < end && isXPathDigit(*mPosition)) { /* just go */ } } newToken = new Token(start, mPosition, Token::NUMBER); } else { switch (*mPosition) { //-- ignore whitespace case SPACE: case TX_TAB: case TX_CR: case TX_LF: ++mPosition; isToken = false; break; case S_QUOTE: case D_QUOTE: { iterator start = mPosition; while (++mPosition < end && *mPosition != *start) { // eat literal } if (mPosition == end) { mPosition = start; return NS_ERROR_XPATH_UNCLOSED_LITERAL; } newToken = new Token(start + 1, mPosition, Token::LITERAL); ++mPosition; } break; case PERIOD: // period can be .., .(DIGITS)+ or ., check next if (++mPosition == end) { newToken = new Token(mPosition - 1, Token::SELF_NODE); } else if (isXPathDigit(*mPosition)) { iterator start = mPosition - 1; while (++mPosition < end && isXPathDigit(*mPosition)) { /* just go */ } newToken = new Token(start, mPosition, Token::NUMBER); } else if (*mPosition == PERIOD) { ++mPosition; newToken = new Token(mPosition - 2, mPosition, Token::PARENT_NODE); } else { newToken = new Token(mPosition - 1, Token::SELF_NODE); } break; case COLON: // QNames are dealt above, must be axis ident if (++mPosition >= end || *mPosition != COLON || prevToken->mType != Token::CNAME) { return NS_ERROR_XPATH_BAD_COLON; } prevToken->mType = Token::AXIS_IDENTIFIER; ++mPosition; isToken = false; break; case FORWARD_SLASH: if (++mPosition < end && *mPosition == FORWARD_SLASH) { ++mPosition; newToken = new Token(mPosition - 2, mPosition, Token::ANCESTOR_OP); } else { newToken = new Token(mPosition - 1, Token::PARENT_OP); } break; case BANG: // can only be != if (++mPosition < end && *mPosition == EQUAL) { ++mPosition; newToken = new Token(mPosition - 2, mPosition, Token::NOT_EQUAL_OP); break; } // Error ! is not not() return NS_ERROR_XPATH_BAD_BANG; case EQUAL: newToken = new Token(mPosition, Token::EQUAL_OP); ++mPosition; break; case L_ANGLE: if (++mPosition == end) { return NS_ERROR_XPATH_UNEXPECTED_END; } if (*mPosition == EQUAL) { ++mPosition; newToken = new Token(mPosition - 2, mPosition, Token::LESS_OR_EQUAL_OP); } else { newToken = new Token(mPosition - 1, Token::LESS_THAN_OP); } break; case R_ANGLE: if (++mPosition == end) { return NS_ERROR_XPATH_UNEXPECTED_END; } if (*mPosition == EQUAL) { ++mPosition; newToken = new Token(mPosition - 2, mPosition, Token::GREATER_OR_EQUAL_OP); } else { newToken = new Token(mPosition - 1, Token::GREATER_THAN_OP); } break; case HYPHEN: newToken = new Token(mPosition, Token::SUBTRACTION_OP); ++mPosition; break; case ASTERISK: if (nextIsOperatorToken(prevToken)) { newToken = new Token(mPosition, Token::MULTIPLY_OP); } else { newToken = new Token(mPosition, Token::CNAME); } ++mPosition; break; case L_PAREN: if (prevToken->mType == Token::CNAME) { const nsDependentSubstring& val = prevToken->Value(); if (val.EqualsLiteral("comment")) { prevToken->mType = Token::COMMENT_AND_PAREN; } else if (val.EqualsLiteral("node")) { prevToken->mType = Token::NODE_AND_PAREN; } else if (val.EqualsLiteral("processing-instruction")) { prevToken->mType = Token::PROC_INST_AND_PAREN; } else if (val.EqualsLiteral("text")) { prevToken->mType = Token::TEXT_AND_PAREN; } else { prevToken->mType = Token::FUNCTION_NAME_AND_PAREN; } isToken = false; } else { newToken = new Token(mPosition, Token::L_PAREN); } ++mPosition; break; case R_PAREN: newToken = new Token(mPosition, Token::R_PAREN); ++mPosition; break; case L_BRACKET: newToken = new Token(mPosition, Token::L_BRACKET); ++mPosition; break; case R_BRACKET: newToken = new Token(mPosition, Token::R_BRACKET); ++mPosition; break; case COMMA: newToken = new Token(mPosition, Token::COMMA); ++mPosition; break; case AT_SIGN: newToken = new Token(mPosition, Token::AT_SIGN); ++mPosition; break; case PLUS: newToken = new Token(mPosition, Token::ADDITION_OP); ++mPosition; break; case VERT_BAR: newToken = new Token(mPosition, Token::UNION_OP); ++mPosition; break; default: // Error, don't grok character :-( return NS_ERROR_XPATH_ILLEGAL_CHAR; } } if (isToken) { NS_ENSURE_TRUE(newToken != mLastItem, NS_ERROR_FAILURE); prevToken = newToken; addToken(newToken); } } // add a endToken to the list newToken = new Token(end, end, Token::END); addToken(newToken); return NS_OK; }