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; } }