blob: b1b068b090cd97e6b8c11eb1e775c90d63cc39ea [file] [log] [blame]
/*
* 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);
}
}
}