/* * Copyright 2005 Sascha Weinreuter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.intellij.lang.xpath; import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; public class XPathParser implements PsiParser { private static final boolean DBG_MODE = Boolean.getBoolean(XPathParser.class.getName() + ".debug") || ApplicationManager.getApplication().isUnitTestMode(); @NotNull public ASTNode parse(IElementType root, PsiBuilder builder) { builder.setDebugMode(DBG_MODE); final PsiBuilder.Marker rootMarker = builder.mark(); if (!builder.eof()) { boolean avt = false; if (builder.getTokenType() == XPathTokenTypes.LBRACE) { builder.advanceLexer(); avt = true; } if (!parseExpr(builder)) { builder.error("XPath expression expected"); } if (avt) { checkMatches(builder, XPathTokenTypes.RBRACE, "'}' expected"); } consumeBadTokens(builder, TokenSet.EMPTY); } rootMarker.done(root); return builder.getTreeBuilt(); } protected boolean parsePrimaryExpr(PsiBuilder builder) { final IElementType tokenType = builder.getTokenType(); if (tokenType == XPathTokenTypes.DOLLAR) { parseVariable(builder); } else if (tokenType == XPathTokenTypes.LPAREN) { parseParenthesizedExpr(builder); } else if (tokenType == XPathTokenTypes.STRING_LITERAL) { parseLiteral(builder); } else if (tokenType == XPathTokenTypes.NUMBER) { parseNumber(builder); } else if (tokenType == XPathTokenTypes.FUNCTION_NAME || tokenType == XPathTokenTypes.EXT_PREFIX) { parseFunction(builder); } else { return false; } return true; } private void parseParenthesizedExpr(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); if (!parseParenExpr(builder)) { builder.error("expression expected"); } checkMatches(builder, XPathTokenTypes.RPAREN, ") expected"); marker.done(XPathElementTypes.PARENTHESIZED_EXPR); } protected boolean parseParenExpr(PsiBuilder builder) { return parseExpr(builder); } protected boolean parseFunction(PsiBuilder builder) { IElementType tokenType = builder.getTokenType(); if (tokenType == XPathTokenTypes.FUNCTION_NAME || tokenType == XPathTokenTypes.EXT_PREFIX) { final PsiBuilder.Marker func = builder.mark(); builder.advanceLexer(); if (builder.getTokenType() == XPathTokenTypes.COL) { builder.advanceLexer(); checkMatches(builder, XPathTokenTypes.FUNCTION_NAME, "function name expected"); } parseArgumentList(builder); func.done(XPathElementTypes.FUNCTION_CALL); return true; } return false; } protected void parseArgumentList(PsiBuilder builder) { checkMatches(builder, XPathTokenTypes.LPAREN, "( expected"); if (builder.getTokenType() != XPathTokenTypes.RPAREN) { if (!parseArgument(builder)) { builder.error("expression expected"); } } while (builder.getTokenType() == XPathTokenTypes.COMMA) { builder.advanceLexer(); if (!parseArgument(builder)) { builder.error("expression expected"); } } checkMatches(builder, XPathTokenTypes.RPAREN, ") expected"); } protected boolean parseArgument(PsiBuilder builder) { return parseExpr(builder); } private static void parseNumber(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); marker.done(XPathElementTypes.NUMBER); } private static void parseLiteral(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); marker.done(XPathElementTypes.STRING); } private static void parseVariable(PsiBuilder builder) { parseVariable(builder, XPathElementTypes.VARIABLE_REFERENCE); } protected static void parseVariable(PsiBuilder builder, IElementType elementType) { final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); if (builder.getTokenType() == XPathTokenTypes.VARIABLE_PREFIX) { builder.advanceLexer(); checkMatches(builder, XPathTokenTypes.COL, "':' expected"); } checkMatches(builder, XPathTokenTypes.VARIABLE_NAME, "variable expected"); marker.done(elementType); } protected boolean parseExpr(PsiBuilder builder) { return parseOrExpr(builder); } protected boolean parseOrExpr(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseAndExpression(builder)) { expr.drop(); return false; } while (builder.getTokenType() == XPathTokenTypes.OR) { makeToken(builder); if (!parseAndExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected boolean parseAndExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseEqualityExpression(builder)) { expr.drop(); return false; } while (builder.getTokenType() == XPathTokenTypes.AND) { makeToken(builder); if (!parseEqualityExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected boolean parseEqualityExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseRelationalExpression(builder)) { expr.drop(); return false; } while (XPathTokenTypes.EQUALITY_OPS.contains(builder.getTokenType())) { makeToken(builder); if (!parseRelationalExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } private boolean parseRelationalExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseAdditiveExpression(builder)) { expr.drop(); return false; } while (XPathTokenTypes.REL_OPS.contains(builder.getTokenType())) { makeToken(builder); if (!parseAdditiveExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected boolean parseAdditiveExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseMultiplicativeExpression(builder)) { expr.drop(); return false; } while (XPathTokenTypes.ADD_OPS.contains(builder.getTokenType())) { makeToken(builder); if (!parseMultiplicativeExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected boolean parseMultiplicativeExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parseUnaryExpression(builder)) { expr.drop(); return false; } while (XPathTokenTypes.MUL_OPS.contains(builder.getTokenType())) { makeToken(builder); if (!parseUnaryExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected boolean parseUnaryExpression(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.MINUS) { final PsiBuilder.Marker expr = builder.mark(); builder.advanceLexer(); if (!parseUnionExpression(builder)) { builder.error("Expression expected"); } expr.done(XPathElementTypes.PREFIX_EXPRESSION); return true; } else { return parseUnionExpression(builder); } } protected boolean parseUnionExpression(final PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parsePathExpression(builder)) { expr.drop(); return false; } while (unionOps().contains(builder.getTokenType())) { makeToken(builder); if (!parsePathExpression(builder)) { builder.error("expression expected"); } expr.done(XPathElementTypes.BINARY_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } protected TokenSet unionOps() { return TokenSet.create(XPathTokenTypes.UNION); } /** * [19] PathExpr ::= LocationPath * | FilterExpr * | FilterExpr '/' RelativeLocationPath * | FilterExpr '//' RelativeLocationPath */ protected boolean parsePathExpression(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); if (!parseLocationPath(builder, false) && !parseAbsoluteLocationPath(builder)) { final PsiBuilder.Marker m2 = builder.mark(); if (!parseFilterExpression(builder)) { m2.drop(); marker.drop(); return false; } if (XPathTokenTypes.PATH_OPS.contains(builder.getTokenType())) { makeToken(builder); if (!parseLocationPath(builder, false, m2)) { builder.error("location path expected"); } marker.done(XPathElementTypes.LOCATION_PATH); } else { m2.drop(); marker.drop(); } } else { marker.done(XPathElementTypes.LOCATION_PATH); } return true; } protected static void makeToken(PsiBuilder builder) { final IElementType tokenType = builder.getTokenType(); final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); marker.done(tokenType); } /** * [3] RelativeLocationPath ::= Step * | RelativeLocationPath '/' Step * | RelativeLocationPath '//' Step */ private boolean parseLocationPath(PsiBuilder builder, boolean isAbsolute, PsiBuilder.Marker... m) { assert m.length <= 1; PsiBuilder.Marker marker = m.length == 1 ? m[0] : builder.mark(); if (isAbsolute) { // root step makeToken(builder); if (builder.getTokenType() == null) { marker.done(XPathElementTypes.STEP); return true; } } if (!parseStep(builder)) { marker.drop(); return false; } marker.done(XPathElementTypes.STEP); marker = marker.precede(); if (XPathTokenTypes.PATH_OPS.contains(builder.getTokenType())) { do { makeToken(builder); if (!parseStep(builder)) { builder.error("location step expected"); } marker.done(XPathElementTypes.STEP); marker = marker.precede(); } while (XPathTokenTypes.PATH_OPS.contains(builder.getTokenType())); } marker.drop(); return true; } /** * [4] Step ::= AxisSpecifier NodeTest Predicate* * | AbbreviatedStep */ private boolean parseStep(PsiBuilder builder) { if (parseAxisSpecifier(builder)) { if (!parseNodeTest(builder)) { builder.error("node test expected"); } while (builder.getTokenType() == XPathTokenTypes.LBRACKET) { parsePredicate(builder); } return true; } else if (parseAbbreviatedStep(builder)) { return true; } return false; } /** * [12] AbbreviatedStep ::= '.' | '..' */ private static boolean parseAbbreviatedStep(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.DOT || builder.getTokenType() == XPathTokenTypes.DOTDOT) { makeToken(builder); return true; } return false; } /** * [7] NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' */ protected boolean parseNodeTest(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); if (!parseNameTest(builder)) { if (!parseNodeType(builder)) { marker.drop(); return false; } } marker.done(XPathElementTypes.NODE_TEST); return true; } protected boolean parseNodeType(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.NODE_TYPE) { final PsiBuilder.Marker m = builder.mark(); builder.advanceLexer(); parseArgumentList(builder); m.done(XPathElementTypes.NODE_TYPE); return true; } return false; } /** * [37] NameTest ::= '*' | NCName ':' '*' | QName */ protected boolean parseNameTest(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.STAR) { return parseWildcard(builder); } else if (builder.getTokenType() == XPathTokenTypes.NCNAME) { builder.advanceLexer(); if (builder.getTokenType() == XPathTokenTypes.COL) { builder.advanceLexer(); if (builder.getTokenType() != XPathTokenTypes.STAR) { if (builder.getTokenType() != XPathTokenTypes.NCNAME) { builder.error("* or NCName expected"); } else { builder.advanceLexer(); } } else { builder.advanceLexer(); } } return true; } return false; } protected boolean parseWildcard(PsiBuilder builder) { builder.advanceLexer(); return true; } /** * [5] AxisSpecifier ::= AxisName '::' | AbbreviatedAxisSpecifier * <p/> * [13] AbbreviatedAxisSpecifier ::= '@'? */ private boolean parseAxisSpecifier(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); final IElementType tokenType = builder.getTokenType(); if (XPathTokenTypes.AXIS.contains(tokenType)) { builder.advanceLexer(); checkMatches(builder, XPathTokenTypes.COLCOL, ":: expected"); } else { if (tokenType == XPathTokenTypes.AT) { builder.advanceLexer(); } else if (tokenType == XPathTokenTypes.DOT || tokenType == XPathTokenTypes.DOTDOT) { marker.drop(); return false; } else { final PsiBuilder.Marker m = builder.mark(); final boolean b = parseNodeTest(builder); m.rollbackTo(); if (!b) { marker.drop(); return false; } } } marker.done(XPathElementTypes.AXIS_SPECIFIER); return true; } /** * [20] FilterExpr ::= PrimaryExpr | FilterExpr Predicate */ private boolean parseFilterExpression(PsiBuilder builder) { PsiBuilder.Marker expr = builder.mark(); if (!parsePrimaryExpr(builder)) { expr.drop(); return false; } while (XPathTokenTypes.LBRACKET == builder.getTokenType()) { parsePredicate(builder); expr.done(XPathElementTypes.FILTER_EXPRESSION); expr = expr.precede(); } expr.drop(); return true; } /** * [8] Predicate ::= '[' PredicateExpr ']' * [9] PredicateExpr ::= Expr */ protected boolean parsePredicate(PsiBuilder builder) { final PsiBuilder.Marker marker = builder.mark(); if (builder.getTokenType() != XPathTokenTypes.LBRACKET) { marker.drop(); return false; } builder.advanceLexer(); if (!parseExpr(builder)) { builder.error("expression expected"); } checkMatches(builder, XPathTokenTypes.RBRACKET, "] expected"); marker.done(XPathElementTypes.PREDICATE); return true; } /** * [2] AbsoluteLocationPath ::= '/' RelativeLocationPath? | AbbreviatedAbsoluteLocationPath */ private boolean parseAbsoluteLocationPath(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.PATH) { parseLocationPath(builder, true); return true; } else { return parseAbbreviatedAbsoluteLocationPath(builder); } } /** * [10] AbbreviatedAbsoluteLocationPath ::= '//' RelativeLocationPath */ private boolean parseAbbreviatedAbsoluteLocationPath(PsiBuilder builder) { if (builder.getTokenType() == XPathTokenTypes.ANY_PATH) { if (!parseLocationPath(builder, true)) { builder.error("location path expected"); } return true; } return false; } private static void consumeBadTokens(PsiBuilder builder, TokenSet nextAccepted) { if (nextAccepted.getTypes().length == 0 && builder.eof()) { return; } if (!nextAccepted.contains(builder.getTokenType())) { builder.error("Unexpected token"); do { if (builder.eof()) { builder.error("Unexpected end of file"); break; } builder.advanceLexer(); } while (!nextAccepted.contains(builder.getTokenType())); } } protected static void checkMatches(final PsiBuilder builder, final IElementType token, final String message) { if (builder.getTokenType() == token) { builder.advanceLexer(); } else { builder.error(message); } } protected static void checkMatches(final PsiBuilder builder, final TokenSet tokens, final String message) { if (tokens.contains(builder.getTokenType())) { builder.advanceLexer(); } else { builder.error(message); } } }