blob: 2021bc943ae06437881d120966dc3b20fd1c0a90 [file] [log] [blame]
package com.intellij.tasks.jira.jql;
import com.intellij.lang.ASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
/**
* @author Mikhail Golubev
*/
public class JqlParser implements PsiParser {
private static final Logger LOG = Logger.getInstance(JqlParser.class);
@NotNull
@Override
public ASTNode parse(IElementType root, PsiBuilder builder) {
//builder.setDebugMode(true);
PsiBuilder.Marker rootMarker = builder.mark();
// Parser should accept empty string
if (!builder.eof()) {
parseQuery(builder);
while (!builder.eof()) {
builder.error("Illegal query part");
builder.advanceLexer();
}
}
rootMarker.done(root);
return builder.getTreeBuilt();
}
private boolean parseQuery(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
parseORClause(builder);
if (builder.getTokenType() == JqlTokenTypes.ORDER_KEYWORD) {
parseOrderBy(builder);
}
marker.done(JqlElementTypes.QUERY);
return true;
}
private boolean parseORClause(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!parseANDClause(builder)) {
marker.drop();
return false;
}
while (advanceIfMatches(builder, JqlTokenTypes.OR_OPERATORS)) {
if (!parseANDClause(builder)) {
builder.error("Expecting clause");
}
marker.done(JqlElementTypes.OR_CLAUSE);
marker = marker.precede();
}
marker.drop();
return true;
}
private boolean parseANDClause(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!parseNOTClause(builder)) {
marker.drop();
return false;
}
while (advanceIfMatches(builder, JqlTokenTypes.AND_OPERATORS)) {
if (!parseNOTClause(builder)) {
builder.error("Expecting clause");
}
marker.done(JqlElementTypes.AND_CLAUSE);
marker = marker.precede();
}
marker.drop();
return true;
}
private boolean parseNOTClause(PsiBuilder builder) {
if (JqlTokenTypes.NOT_OPERATORS.contains(builder.getTokenType())) {
PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
if (!parseNOTClause(builder)) {
builder.error("Expecting clause");
}
marker.done(JqlElementTypes.NOT_CLAUSE);
return true;
}
else if (builder.getTokenType() == JqlTokenTypes.LPAR) {
return parseSubClause(builder);
}
return parseTerminalClause(builder);
}
private boolean parseSubClause(PsiBuilder builder) {
LOG.assertTrue(builder.getTokenType() == JqlTokenTypes.LPAR);
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.LPAR)) {
marker.drop();
return false;
}
parseORClause(builder);
if (!advanceIfMatches(builder, JqlTokenTypes.RPAR)) {
builder.error("Expecting ')'");
}
marker.done(JqlElementTypes.SUB_CLAUSE);
return true;
}
private boolean parseTerminalClause(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!parseFieldName(builder)) {
marker.drop();
return false;
}
if (advanceIfMatches(builder, JqlTokenTypes.IS_KEYWORD)) {
advanceIfMatches(builder, JqlTokenTypes.NOT_KEYWORD);
parseOperand(builder);
}
else if (advanceIfMatches(builder, JqlTokenTypes.SIMPLE_OPERATORS) || advanceIfMatches(builder, JqlTokenTypes.IN_KEYWORD)) {
parseOperand(builder);
}
else if (advanceIfMatches(builder, JqlTokenTypes.NOT_KEYWORD)) {
if (!advanceIfMatches(builder, JqlTokenTypes.IN_KEYWORD)) {
builder.error("Expecting 'in'");
}
parseOperand(builder);
}
else if (builder.getTokenType() == JqlTokenTypes.WAS_KEYWORD) {
parseWASClause(builder);
marker.done(JqlElementTypes.WAS_CLAUSE);
return true;
}
else if (builder.getTokenType() == JqlTokenTypes.CHANGED_KEYWORD) {
parseCHANGEDClause(builder);
marker.done(JqlElementTypes.CHANGED_CLAUSE);
return true;
}
else {
builder.error("Expecting operator");
}
marker.done(JqlElementTypes.SIMPLE_CLAUSE);
return true;
}
private void parseCHANGEDClause(PsiBuilder builder) {
LOG.assertTrue(builder.getTokenType() == JqlTokenTypes.CHANGED_KEYWORD);
if (!advanceIfMatches(builder, JqlTokenTypes.CHANGED_KEYWORD)) {
return;
}
while (JqlTokenTypes.HISTORY_PREDICATES.contains(builder.getTokenType())) {
parseHistoryPredicate(builder);
}
}
private void parseWASClause(PsiBuilder builder) {
LOG.assertTrue(builder.getTokenType() == JqlTokenTypes.WAS_KEYWORD);
if (!advanceIfMatches(builder, JqlTokenTypes.WAS_KEYWORD)) {
return;
}
advanceIfMatches(builder, JqlTokenTypes.NOT_KEYWORD);
advanceIfMatches(builder, JqlTokenTypes.IN_KEYWORD);
parseOperand(builder);
while (JqlTokenTypes.HISTORY_PREDICATES.contains(builder.getTokenType())) {
parseHistoryPredicate(builder);
}
}
private void parseHistoryPredicate(PsiBuilder builder) {
LOG.assertTrue(JqlTokenTypes.HISTORY_PREDICATES.contains(builder.getTokenType()));
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.HISTORY_PREDICATES)) {
marker.drop();
return;
}
parseOperand(builder);
marker.done(JqlElementTypes.HISTORY_PREDICATE);
}
private boolean parseOperand(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
boolean parsed = true;
if (builder.getTokenType() == JqlTokenTypes.LPAR) {
marker.drop();
parsed = parseList(builder);
}
else if (advanceIfMatches(builder, JqlTokenTypes.EMPTY_VALUES)) {
marker.done(JqlElementTypes.EMPTY);
}
else if (advanceIfMatches(builder, JqlTokenTypes.LITERALS)) {
if (builder.getTokenType() == JqlTokenTypes.LPAR) {
marker.done(JqlElementTypes.IDENTIFIER);
marker = marker.precede();
parseArgumentList(builder);
marker.done(JqlElementTypes.FUNCTION_CALL);
}
else {
marker.done(JqlElementTypes.LITERAL);
}
}
else {
marker.drop();
parsed = false;
}
if (!parsed) {
builder.error("Expecting either literal, list or function call");
}
return parsed;
}
private boolean parseList(PsiBuilder builder) {
LOG.assertTrue(builder.getTokenType() == JqlTokenTypes.LPAR);
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.LPAR)) {
marker.drop();
return false;
}
if (parseOperand(builder)) {
while (advanceIfMatches(builder, JqlTokenTypes.COMMA)) {
if (!parseOperand(builder)) {
break;
}
}
}
if (!advanceIfMatches(builder, JqlTokenTypes.RPAR)) {
builder.error("Expecting ')'");
}
marker.done(JqlElementTypes.LIST);
return true;
}
private boolean parseFieldName(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.VALID_FIELD_NAMES)) {
builder.error("Expecting field name");
marker.drop();
return false;
}
marker.done(JqlElementTypes.IDENTIFIER);
return true;
}
private void parseArgumentList(PsiBuilder builder) {
LOG.assertTrue(builder.getTokenType() == JqlTokenTypes.LPAR);
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.LPAR)) {
marker.drop();
return;
}
// empty argument list
if (advanceIfMatches(builder, JqlTokenTypes.RPAR)) {
marker.done(JqlElementTypes.ARGUMENT_LIST);
return;
}
PsiBuilder.Marker argument = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.VALID_ARGUMENTS)) {
builder.error("Expecting argument");
argument.drop();
}
else {
// valid argument is always literal, at least in current implementation of JQU
argument.done(JqlElementTypes.LITERAL);
while (advanceIfMatches(builder, JqlTokenTypes.COMMA)) {
argument = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.VALID_ARGUMENTS)) {
marker.drop();
builder.error("Expecting argument");
break;
}
argument.done(JqlElementTypes.LITERAL);
}
}
if (!advanceIfMatches(builder, JqlTokenTypes.RPAR)) {
builder.error("Expecting ')'");
}
marker.done(JqlElementTypes.ARGUMENT_LIST);
}
private void parseOrderBy(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!advanceIfMatches(builder, JqlTokenTypes.ORDER_KEYWORD)) {
marker.drop();
return;
}
if (!advanceIfMatches(builder, JqlTokenTypes.BY_KEYWORD)) {
builder.error("Expecting 'by'");
marker.done(JqlElementTypes.ORDER_BY);
return;
}
if (parseSortKey(builder)) {
while (advanceIfMatches(builder, JqlTokenTypes.COMMA)) {
if (!parseSortKey(builder)) {
break;
}
}
}
marker.done(JqlElementTypes.ORDER_BY);
}
private boolean parseSortKey(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
if (!parseFieldName(builder)) {
marker.drop();
return false;
}
advanceIfMatches(builder, JqlTokenTypes.SORT_ORDERS);
marker.done(JqlElementTypes.SORT_KEY);
return true;
}
private boolean advanceIfMatches(PsiBuilder builder, IElementType type) {
if (builder.getTokenType() == type) {
builder.advanceLexer();
return true;
}
return false;
}
private boolean advanceIfMatches(PsiBuilder builder, TokenSet set) {
if (set.contains(builder.getTokenType())) {
builder.advanceLexer();
return true;
}
return false;
}
}