| /* |
| * Copyright 2000-2011 JetBrains s.r.o. |
| * |
| * 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.PsiBuilder; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.tree.TokenSet; |
| import org.jetbrains.annotations.Nullable; |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: sweinreuter |
| * Date: 03.01.11 |
| */ |
| public class XPath2Parser extends XPathParser { |
| @Override |
| protected boolean parseExpr(PsiBuilder builder) { |
| final PsiBuilder.Marker seq = builder.mark(); |
| final boolean b = parseExprSingle(builder); |
| if (b && builder.getTokenType() == XPathTokenTypes.COMMA) { |
| do { |
| builder.advanceLexer(); |
| if (!parseExprSingle(builder)) { |
| builder.error("Expression expected"); |
| } |
| } while (builder.getTokenType() == XPathTokenTypes.COMMA); |
| seq.done(XPath2ElementTypes.SEQUENCE); |
| } else { |
| seq.drop(); |
| } |
| return b; |
| } |
| |
| @Override |
| protected boolean parseParenExpr(PsiBuilder builder) { |
| if (builder.getTokenType() == XPathTokenTypes.RPAREN) { |
| builder.mark().done(XPath2ElementTypes.SEQUENCE); |
| return true; |
| } |
| return super.parseParenExpr(builder); |
| } |
| |
| @Override |
| protected boolean parsePrimaryExpr(PsiBuilder builder) { |
| if (builder.getTokenType() == XPathTokenTypes.DOT) { |
| PsiBuilder.Marker mark = builder.mark(); |
| builder.advanceLexer(); |
| mark.done(XPath2ElementTypes.CONTEXT_ITEM); |
| return true; |
| } else { |
| return super.parsePrimaryExpr(builder); |
| } |
| } |
| |
| private boolean parseExprSingle(PsiBuilder builder) { |
| if (builder.getTokenType() == XPath2TokenTypes.FOR) { |
| return parseForExpr(builder); |
| } else if (XPath2TokenTypes.QUANTIFIERS.contains(builder.getTokenType())) { |
| return parseQuantifiedExpr(builder); |
| } else if (builder.getTokenType() == XPath2TokenTypes.IF) { |
| return parseIfExpr(builder); |
| } |
| return parseOrExpr(builder); |
| } |
| |
| @Override |
| protected boolean parseArgument(PsiBuilder builder) { |
| return parseExprSingle(builder); |
| } |
| |
| private boolean parseIfExpr(PsiBuilder builder) { |
| final PsiBuilder.Marker mark = builder.mark(); |
| checkMatches(builder, XPath2TokenTypes.IF, "'if' expected"); |
| checkMatches(builder, XPathTokenTypes.LPAREN, "'(' expected"); |
| if (!parseExpr(builder)) { |
| builder.error("expression expected"); |
| } |
| checkMatches(builder, XPathTokenTypes.RPAREN, "')' expected"); |
| checkMatches(builder, XPath2TokenTypes.THEN, "'then' expected"); |
| if (!parseExprSingle(builder)) { |
| builder.error("expression expected"); |
| } |
| checkMatches(builder, XPath2TokenTypes.ELSE, "'else' expected"); |
| if (!parseExprSingle(builder)) { |
| builder.error("expression expected"); |
| } |
| mark.done(XPath2ElementTypes.IF); |
| return true; |
| } |
| |
| private boolean parseQuantifiedExpr(PsiBuilder builder) { |
| PsiBuilder.Marker mark = builder.mark(); |
| checkMatches(builder, XPath2TokenTypes.QUANTIFIERS, "'every' or 'some' expected"); |
| |
| parseBindingSequence(builder); |
| |
| checkMatches(builder, XPath2TokenTypes.SATISFIES, "'satisfies' expected"); |
| |
| final PsiBuilder.Marker ret = builder.mark(); |
| if (!parseExprSingle(builder)) { |
| builder.error("expression expected"); |
| } |
| |
| ret.done(XPath2ElementTypes.BODY); |
| mark.done(XPath2ElementTypes.QUANTIFIED); |
| return true; |
| } |
| |
| protected boolean parseForExpr(PsiBuilder builder) { |
| final PsiBuilder.Marker mark = builder.mark(); |
| checkMatches(builder, XPath2TokenTypes.FOR, "'for' expected"); |
| |
| parseBindingSequence(builder); |
| |
| final PsiBuilder.Marker ret = builder.mark(); |
| checkMatches(builder, XPath2TokenTypes.RETURN, "'return' expected"); |
| if (!parseExprSingle(builder)) { |
| builder.error("expression expected"); |
| } |
| |
| ret.done(XPath2ElementTypes.BODY); |
| mark.done(XPath2ElementTypes.FOR); |
| return true; |
| } |
| |
| private void parseBindingSequence(PsiBuilder builder) { |
| do { |
| if (builder.getTokenType() == XPathTokenTypes.COMMA) { |
| builder.advanceLexer(); |
| } |
| |
| final PsiBuilder.Marker bindingSeq = builder.mark(); |
| parseVariableDecl(builder); |
| |
| checkMatches(builder, XPath2TokenTypes.IN, "'in' expected"); |
| |
| if (!parseExprSingle(builder)) { |
| builder.error("expression expected"); |
| } |
| bindingSeq.done(XPath2ElementTypes.BINDING_SEQ); |
| } while (builder.getTokenType() == XPathTokenTypes.COMMA); |
| } |
| |
| @Override |
| protected boolean parseEqualityExpression(PsiBuilder builder) { |
| return parseComparisonExpr(builder); |
| } |
| |
| private boolean parseComparisonExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseRangeExpression(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| while (XPath2TokenTypes.COMP_OPS.contains(builder.getTokenType())) { |
| makeToken(builder); |
| if (!parseRangeExpression(builder)) { |
| builder.error("expression expected"); |
| } |
| expr.done(XPathElementTypes.BINARY_EXPRESSION); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| return true; |
| } |
| |
| protected boolean parseRangeExpression(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseAdditiveExpression(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| while (builder.getTokenType() == XPath2TokenTypes.TO) { |
| makeToken(builder); |
| if (!parseAdditiveExpression(builder)) { |
| builder.error("expression expected"); |
| } |
| expr.done(XPath2ElementTypes.RANGE_EXPRESSION); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| return true; |
| } |
| |
| @Override |
| protected boolean parseMultiplicativeExpression(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseUnionExpression(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| while (XPath2TokenTypes.MULT_OPS.contains(builder.getTokenType())) { |
| makeToken(builder); |
| if (!parseUnionExpression(builder)) { |
| builder.error("expression expected"); |
| } |
| expr.done(XPathElementTypes.BINARY_EXPRESSION); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| return true; |
| } |
| |
| @Override |
| protected boolean parseUnionExpression(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseIntersectExceptExpr(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| while (XPath2TokenTypes.UNION_OPS.contains(builder.getTokenType())) { |
| makeToken(builder); |
| if (!parseIntersectExceptExpr(builder)) { |
| builder.error("expression expected"); |
| } |
| expr.done(XPathElementTypes.BINARY_EXPRESSION); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| return true; |
| } |
| |
| protected boolean parseIntersectExceptExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseInstanceofExpr(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| while (XPath2TokenTypes.INTERSECT_EXCEPT.contains(builder.getTokenType())) { |
| makeToken(builder); |
| if (!parseInstanceofExpr(builder)) { |
| builder.error("expression expected"); |
| } |
| expr.done(XPathElementTypes.BINARY_EXPRESSION); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| return true; |
| } |
| |
| protected boolean parseInstanceofExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseTreatExpr(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| if (builder.getTokenType() == XPath2TokenTypes.INSTANCE) { |
| builder.advanceLexer(); |
| checkMatches(builder, XPath2TokenTypes.OF, "'of' expected"); |
| if (!parseSequenceType(builder)) { |
| builder.error("sequence type expected"); |
| } |
| expr.done(XPath2ElementTypes.INSTANCE_OF); |
| } else { |
| expr.drop(); |
| } |
| |
| return true; |
| } |
| |
| protected boolean parseTreatExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseCastableExpr(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| if (builder.getTokenType() == XPath2TokenTypes.TREAT) { |
| builder.advanceLexer(); |
| |
| checkMatches(builder, XPath2TokenTypes.AS, "'as' expected"); |
| if (!parseSequenceType(builder)) { |
| builder.error("sequence type expected"); |
| } |
| expr.done(XPath2ElementTypes.TREAT_AS); |
| } else { |
| expr.drop(); |
| } |
| |
| return true; |
| } |
| |
| protected boolean parseCastableExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseCastExpr(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| if (builder.getTokenType() == XPath2TokenTypes.CASTABLE) { |
| builder.advanceLexer(); |
| |
| checkMatches(builder, XPath2TokenTypes.AS, "'as' expected"); |
| parseSingleType(builder); |
| expr.done(XPath2ElementTypes.CASTABLE_AS); |
| } else { |
| expr.drop(); |
| } |
| |
| return true; |
| } |
| |
| protected boolean parseCastExpr(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| if (!parseUnaryExpression(builder)) { |
| expr.drop(); |
| return false; |
| } |
| |
| if (builder.getTokenType() == XPath2TokenTypes.CAST) { |
| builder.advanceLexer(); |
| |
| checkMatches(builder, XPath2TokenTypes.AS, "'as' expected"); |
| parseSingleType(builder); |
| expr.done(XPath2ElementTypes.CAST_AS); |
| } else { |
| expr.drop(); |
| } |
| |
| return true; |
| } |
| |
| private static void parseSingleType(PsiBuilder builder) { |
| PsiBuilder.Marker mark = builder.mark(); |
| if (!parseQName(builder)) { |
| builder.error("QName expected"); |
| } |
| if (builder.getTokenType() == XPath2TokenTypes.QUEST) { |
| builder.advanceLexer(); |
| } |
| mark.done(XPath2ElementTypes.SINGLE_TYPE); |
| |
| if (builder.getTokenType() == XPathTokenTypes.STAR) { |
| builder.remapCurrentToken(XPathTokenTypes.MULT); |
| } |
| } |
| |
| private boolean parseSequenceType(PsiBuilder builder) { |
| PsiBuilder.Marker mark = builder.mark(); |
| |
| final IElementType type = parseItemOrEmptySequenceType(builder); |
| if (type != null) { |
| if (type == XPath2TokenTypes.ITEM) { |
| if (XPath2TokenTypes.OCCURRENCE_OPS.contains(builder.getTokenType())) { |
| makeToken(builder); |
| } |
| } |
| |
| mark.done(XPath2ElementTypes.SEQUENCE_TYPE); |
| return true; |
| } else if (parseNodeType(builder) || parseQName(builder)) { |
| if (builder.getTokenType() == XPathTokenTypes.MULT) { |
| builder.remapCurrentToken(XPathTokenTypes.STAR); |
| } |
| if (XPath2TokenTypes.OCCURRENCE_OPS.contains(builder.getTokenType())) { |
| makeToken(builder); |
| } |
| mark.done(XPath2ElementTypes.SEQUENCE_TYPE); |
| return true; |
| } |
| |
| mark.drop(); |
| return false; |
| } |
| |
| @Nullable |
| private IElementType parseItemOrEmptySequenceType(PsiBuilder builder) { |
| final IElementType tokenType = builder.getTokenType(); |
| if (tokenType == XPath2TokenTypes.ITEM || tokenType == XPath2TokenTypes.EMPTY_SEQUENCE) { |
| final PsiBuilder.Marker mark = builder.mark(); |
| builder.advanceLexer(); |
| parseArgumentList(builder); |
| mark.done(XPath2ElementTypes.ITEM_OR_EMPTY_SEQUENCE); |
| return tokenType; |
| } |
| return null; |
| } |
| |
| private static boolean parseQName(PsiBuilder builder) { |
| if (builder.getTokenType() == XPathTokenTypes.NCNAME) { |
| builder.advanceLexer(); |
| if (builder.getTokenType() == XPathTokenTypes.COL) { |
| builder.advanceLexer(); |
| checkMatches(builder, XPathTokenTypes.NCNAME, "'NCName expected"); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| protected boolean parseUnaryExpression(PsiBuilder builder) { |
| if (XPathTokenTypes.ADD_OPS.contains(builder.getTokenType())) { |
| PsiBuilder.Marker expr = builder.mark(); |
| do { |
| builder.advanceLexer(); |
| |
| if (!parseUnaryExpression(builder)) { |
| builder.error("Expression expected"); |
| } |
| expr.done(XPathElementTypes.PREFIX_EXPRESSION); |
| expr = expr.precede(); |
| } while (XPathTokenTypes.ADD_OPS.contains(builder.getTokenType())); |
| expr.drop(); |
| return true; |
| } |
| return parseValueExpression(builder); |
| } |
| |
| private boolean parseValueExpression(PsiBuilder builder) { |
| return parsePathExpression(builder); |
| } |
| |
| private static void parseVariableDecl(PsiBuilder builder) { |
| parseVariable(builder, XPath2ElementTypes.VARIABLE_DECL); |
| } |
| |
| @Override |
| protected boolean parsePathExpression(PsiBuilder builder) { |
| final PsiBuilder.Marker marker = builder.mark(); |
| if (builder.getTokenType() == XPathTokenTypes.PATH) { |
| builder.advanceLexer(); |
| parseRelativePathExpr(builder, false); |
| marker.done(XPathElementTypes.LOCATION_PATH); |
| return true; |
| } else if (builder.getTokenType() == XPathTokenTypes.ANY_PATH) { |
| builder.advanceLexer(); |
| if (!parseRelativePathExpr(builder, false)) { |
| builder.error("relative path expected"); |
| } |
| marker.done(XPathElementTypes.LOCATION_PATH); |
| return true; |
| } else { |
| marker.drop(); |
| return parseRelativePathExpr(builder, true); |
| } |
| } |
| |
| private boolean parseRelativePathExpr(PsiBuilder builder, boolean pathRequired) { |
| PsiBuilder.Marker path = builder.mark(); |
| boolean nameLocationPath = false; |
| |
| PsiBuilder.Marker expr = builder.mark(); |
| if (parseAxisStep(builder)) { |
| expr.done(XPathElementTypes.STEP); |
| expr = expr.precede(); |
| if (pathRequired) nameLocationPath = true; |
| } else if (!parseFilterExpr(builder)) { |
| expr.drop(); |
| path.drop(); |
| return false; |
| } |
| |
| while (XPathTokenTypes.PATH_OPS.contains(builder.getTokenType())) { |
| if (pathRequired) nameLocationPath = true; |
| |
| makeToken(builder); |
| if (!parseStepExpr(builder)) { |
| builder.error("expression expected"); |
| } |
| |
| expr.done(XPathElementTypes.STEP); |
| expr = expr.precede(); |
| } |
| |
| expr.drop(); |
| |
| if (nameLocationPath) { |
| path.done(XPathElementTypes.LOCATION_PATH); |
| } else { |
| path.drop(); |
| } |
| return true; |
| } |
| |
| private boolean parseStepExpr(PsiBuilder builder) { |
| return parseFilterExpr(builder) || parseAxisStep(builder); |
| } |
| |
| private boolean parseAxisStep(PsiBuilder builder) { |
| PsiBuilder.Marker expr = builder.mark(); |
| |
| final PsiBuilder.Marker mark = builder.mark(); |
| if (XPathTokenTypes.AXIS.contains(builder.getTokenType())) { |
| builder.advanceLexer(); |
| mark.done(XPathElementTypes.AXIS_SPECIFIER); |
| checkMatches(builder, XPathTokenTypes.COLCOL, ":: expected"); |
| if (!parseNodeTest(builder)) { |
| builder.error("node test expected"); |
| } |
| } else if (builder.getTokenType() == XPathTokenTypes.AT) { |
| builder.advanceLexer(); |
| mark.done(XPathElementTypes.AXIS_SPECIFIER); |
| if (!parseNodeTest(builder)) { |
| builder.error("node test expected"); |
| } |
| } else if (builder.getTokenType() == XPathTokenTypes.DOTDOT) { |
| mark.drop(); |
| builder.advanceLexer(); |
| } else { |
| mark.done(XPathElementTypes.AXIS_SPECIFIER); |
| |
| if (!parseNodeTest(builder)) { |
| mark.rollbackTo(); |
| expr.drop(); |
| return false; |
| } |
| } |
| |
| while (XPathTokenTypes.LBRACKET == builder.getTokenType()) { |
| parsePredicate(builder); |
| |
| expr.done(XPathElementTypes.FILTER_EXPRESSION); |
| expr = expr.precede(); |
| } |
| expr.drop(); |
| |
| return true; |
| } |
| |
| private boolean parseFilterExpr(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; |
| } |
| |
| @Override |
| protected boolean parseWildcard(PsiBuilder builder) { |
| builder.advanceLexer(); |
| |
| if (builder.getTokenType() == XPathTokenTypes.COL) { |
| builder.advanceLexer(); |
| if (builder.getTokenType() != XPathTokenTypes.NCNAME) { |
| builder.error("NCName expected"); |
| } |
| builder.advanceLexer(); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected TokenSet unionOps() { |
| return XPath2TokenTypes.UNION_OPS; |
| } |
| } |