diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java index 7b0f8a48dee9..d5d576b36df1 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java @@ -23,417 +23,462 @@ import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@SuppressWarnings({ "RedundantIfStatement" }) +@SuppressWarnings({"RedundantIfStatement"}) public class RegExpParser implements PsiParser { private boolean myAllowDanglingMetacharacters; + private boolean myAllowOmitNumbersInQuantifiers; public void setAllowDanglingMetacharacters(boolean allowDanglingMetacharacters) { myAllowDanglingMetacharacters = allowDanglingMetacharacters; } - @NotNull - public ASTNode parse(IElementType root, PsiBuilder builder) { + public void setAllowOmitNumbersInQuantifiers(boolean allowOmitNumbersInQuantifiers) { + myAllowOmitNumbersInQuantifiers = allowOmitNumbersInQuantifiers; + } + + @NotNull + public ASTNode parse(IElementType root, PsiBuilder builder) { // builder.setDebugMode(true); - final PsiBuilder.Marker rootMarker = builder.mark(); + final PsiBuilder.Marker rootMarker = builder.mark(); - parsePattern(builder); + parsePattern(builder); - while (!builder.eof()) { - patternExpected(builder); - builder.advanceLexer(); - } - - rootMarker.done(root); - return builder.getTreeBuilt(); + while (!builder.eof()) { + patternExpected(builder); + builder.advanceLexer(); } + rootMarker.done(root); + return builder.getTreeBuilt(); + } - /** - * PATTERN ::= BRANCH "|" PATTERN | BRANCH - */ - private boolean parsePattern(PsiBuilder builder) { - final PsiBuilder.Marker marker = builder.mark(); - if (!parseBranch(builder)) { - marker.drop(); - return false; - } + /** + * PATTERN ::= BRANCH "|" PATTERN | BRANCH + */ + private boolean parsePattern(PsiBuilder builder) { + final PsiBuilder.Marker marker = builder.mark(); - while (builder.getTokenType() == RegExpTT.UNION) { - builder.advanceLexer(); - if (!parseBranch(builder)) { - // TODO: no test coverage - patternExpected(builder); - break; - } - } - - marker.done(RegExpElementTypes.PATTERN); - - return true; + if (!parseBranch(builder)) { + marker.drop(); + return false; } - /** - * BRANCH ::= ATOM BRANCH | "" - */ - @SuppressWarnings({ "StatementWithEmptyBody" }) - private boolean parseBranch(PsiBuilder builder) { - PsiBuilder.Marker marker = builder.mark(); + while (builder.getTokenType() == RegExpTT.UNION) { + builder.advanceLexer(); + if (!parseBranch(builder)) { + // TODO: no test coverage + patternExpected(builder); + break; + } + } - if (!parseAtom(builder)) { - final IElementType token = builder.getTokenType(); - if (token == RegExpTT.GROUP_END || token == RegExpTT.UNION || token == null) { - // empty branches are allowed - marker.done(RegExpElementTypes.BRANCH); - return true; - } - marker.drop(); - return false; - } + marker.done(RegExpElementTypes.PATTERN); - for (;parseAtom(builder);) ; + return true; + } + /** + * BRANCH ::= ATOM BRANCH | "" + */ + @SuppressWarnings({"StatementWithEmptyBody"}) + private boolean parseBranch(PsiBuilder builder) { + PsiBuilder.Marker marker = builder.mark(); + + if (!parseAtom(builder)) { + final IElementType token = builder.getTokenType(); + if (token == RegExpTT.GROUP_END || token == RegExpTT.UNION || token == null) { + // empty branches are allowed marker.done(RegExpElementTypes.BRANCH); return true; + } + marker.drop(); + return false; } - /** - * ATOM ::= CLOSURE | GROUP - * CLOSURE ::= GROUP QUANTIFIER - */ - private boolean parseAtom(PsiBuilder builder) { - PsiBuilder.Marker marker = parseGroup(builder); + for (; parseAtom(builder);) ; - if (marker == null) { - return false; - } - marker = marker.precede(); + marker.done(RegExpElementTypes.BRANCH); + return true; + } - if (parseQuantifier(builder)) { - marker.done(RegExpElementTypes.CLOSURE); - } else { - marker.drop(); - } + /** + * ATOM ::= CLOSURE | GROUP + * CLOSURE ::= GROUP QUANTIFIER + */ + private boolean parseAtom(PsiBuilder builder) { + PsiBuilder.Marker marker = parseGroup(builder); - return true; + if (marker == null) { + return false; + } + marker = marker.precede(); + + if (parseQuantifier(builder)) { + marker.done(RegExpElementTypes.CLOSURE); + } + else { + marker.drop(); } - /** - * QUANTIFIER ::= Q TYPE | "" - * Q ::= "{" BOUND "}" | "*" | "?" | "+" - * BOUND ::= NUM | NUM "," | NUM "," NUM - * TYPE ::= "?" | "+" | "" - */ - private boolean parseQuantifier(PsiBuilder builder) { - final PsiBuilder.Marker marker = builder.mark(); + return true; + } - if (builder.getTokenType() == RegExpTT.LBRACE) { - builder.advanceLexer(); - if (builder.getTokenType() != RegExpTT.NUMBER && myAllowDanglingMetacharacters) { - marker.done(RegExpTT.CHARACTER); - return true; - } - checkMatches(builder, RegExpTT.NUMBER, "Number expected"); - if (builder.getTokenType() == RegExpTT.RBRACE) { - builder.advanceLexer(); - parseQuantifierType(builder); - marker.done(RegExpElementTypes.QUANTIFIER); - } else { - checkMatches(builder, RegExpTT.COMMA, "',' expected"); - if (builder.getTokenType() == RegExpTT.RBRACE) { - builder.advanceLexer(); - parseQuantifierType(builder); - marker.done(RegExpElementTypes.QUANTIFIER); - } else if (builder.getTokenType() == RegExpTT.NUMBER) { - builder.advanceLexer(); - checkMatches(builder, RegExpTT.RBRACE, "'}' expected"); - parseQuantifierType(builder); - marker.done(RegExpElementTypes.QUANTIFIER); - } else { - builder.error("'}' or number expected"); - marker.done(RegExpElementTypes.QUANTIFIER); - return true; - } - } - } else if (RegExpTT.QUANTIFIERS.contains(builder.getTokenType())) { - builder.advanceLexer(); - parseQuantifierType(builder); - marker.done(RegExpElementTypes.QUANTIFIER); - } else { - marker.drop(); - return false; - } + /** + * QUANTIFIER ::= Q TYPE | "" + * Q ::= "{" BOUND "}" | "*" | "?" | "+" + * BOUND ::= NUM | NUM "," | NUM "," NUM + * TYPE ::= "?" | "+" | "" + */ + private boolean parseQuantifier(PsiBuilder builder) { + final PsiBuilder.Marker marker = builder.mark(); - return true; - } - - private static void parseQuantifierType(PsiBuilder builder) { - if (builder.getTokenType() == RegExpTT.PLUS) { - builder.advanceLexer(); - } else if (builder.getTokenType() == RegExpTT.QUEST) { - builder.advanceLexer(); - } else { - if (RegExpTT.QUANTIFIERS.contains(builder.getTokenType())) { - builder.error("Dangling metacharacter"); - } - } - } - - /** - * CLASS ::= "[" NEGATION DEFLIST "]" - * NEGATION ::= "^" | "" - * DEFLIST ::= INTERSECTION DEFLIST - * INTERSECTION ::= INTERSECTION "&&" CLASSDEF | CLASSDEF - * CLASSDEF ::= CLASS | SIMPLE_CLASSDEF | "" - * SIMPLE_CLASSDEF ::= CHARACTER | CHARACTER "-" CLASSDEF - */ - private PsiBuilder.Marker parseClass(PsiBuilder builder) { - final PsiBuilder.Marker marker = builder.mark(); + if (builder.getTokenType() == RegExpTT.LBRACE) { + builder.advanceLexer(); + boolean minOmitted = false; + if (builder.getTokenType() == RegExpTT.COMMA && myAllowOmitNumbersInQuantifiers) { + minOmitted = true; builder.advanceLexer(); - - if (builder.getTokenType() == RegExpTT.CARET) { - builder.advanceLexer(); - } - - // DEFLIST - if (parseClassIntersection(builder)) { - while (RegExpTT.CHARACTERS2.contains(builder.getTokenType()) - || builder.getTokenType() == RegExpTT.CLASS_BEGIN - || builder.getTokenType() == RegExpTT.PROPERTY) - { - parseClassIntersection(builder); - } - } - - checkMatches(builder, RegExpTT.CLASS_END, "Unclosed character class"); - marker.done(RegExpElementTypes.CLASS); - return marker; - } - - private boolean parseClassIntersection(PsiBuilder builder) { - PsiBuilder.Marker marker = builder.mark(); - - if (!parseClassdef(builder, false)) { - marker.drop(); - return false; - } - while (RegExpTT.ANDAND == builder.getTokenType()) { - builder.advanceLexer(); - parseClassdef(builder, true); - marker.done(RegExpElementTypes.INTERSECTION); - marker = marker.precede(); - } - - marker.drop(); + } + else if (builder.getTokenType() != RegExpTT.NUMBER && myAllowDanglingMetacharacters) { + marker.done(RegExpTT.CHARACTER); return true; - } - - private boolean parseClassdef(PsiBuilder builder, boolean mayBeEmpty) { - final IElementType token = builder.getTokenType(); - if (token == RegExpTT.CLASS_BEGIN) { - parseClass(builder); - } else if (RegExpTT.CHARACTERS2.contains(token)) { - parseSimpleClassdef(builder); - } else if (token == RegExpTT.PROPERTY) { - parseProperty(builder); - } else if (mayBeEmpty) { - // TODO: no test coverage - return true; - } else { - return false; - } - return true; - } - - private void parseSimpleClassdef(PsiBuilder builder) { - assert RegExpTT.CHARACTERS2.contains(builder.getTokenType()); - - final PsiBuilder.Marker marker = builder.mark(); - makeChar(builder); - - IElementType t = builder.getTokenType(); - if (t == RegExpTT.MINUS) { - final PsiBuilder.Marker m = builder.mark(); - builder.advanceLexer(); - - t = builder.getTokenType(); - if (RegExpTT.CHARACTERS2.contains(t)) { - m.drop(); - makeChar(builder); - marker.done(RegExpElementTypes.CHAR_RANGE); - } else { - marker.drop(); - m.done(t == RegExpTT.CHAR_CLASS ? - RegExpElementTypes.SIMPLE_CLASS : - RegExpElementTypes.CHAR); - - if (t == RegExpTT.CLASS_END) { // [a-] - return; - } else if (t == RegExpTT.CLASS_BEGIN) { // [a-[b]] - if (parseClassdef(builder, false)) { - return; - } - } - builder.error("Illegal character range"); - } - } else { - marker.drop(); - } - } - - private static void makeChar(PsiBuilder builder) { - final IElementType t = builder.getTokenType(); - PsiBuilder.Marker m = builder.mark(); + } + else { + checkMatches(builder, RegExpTT.NUMBER, "Number expected"); + } + if (builder.getTokenType() == RegExpTT.RBRACE) { builder.advanceLexer(); - m.done(t == RegExpTT.CHAR_CLASS ? - RegExpElementTypes.SIMPLE_CLASS : - RegExpElementTypes.CHAR); - } - - /** - * GROUP ::= "(" PATTERN ")" | TERM - * TERM ::= "." | "$" | "^" | CHAR | CLASS | BACKREF - */ - @Nullable - private PsiBuilder.Marker parseGroup(PsiBuilder builder) { - final IElementType type = builder.getTokenType(); - - final PsiBuilder.Marker marker = builder.mark(); - - if (RegExpTT.GROUPS.contains(type)) { - builder.advanceLexer(); - if (!parsePattern(builder)) { - patternExpected(builder); - } else { - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); - } - marker.done(RegExpElementTypes.GROUP); - } else if (type == RegExpTT.SET_OPTIONS) { - builder.advanceLexer(); - - final PsiBuilder.Marker o = builder.mark(); - if (builder.getTokenType() == RegExpTT.OPTIONS_ON) { - builder.advanceLexer(); - } - if (builder.getTokenType() == RegExpTT.OPTIONS_OFF) { - builder.advanceLexer(); - } - o.done(RegExpElementTypes.OPTIONS); - - if (builder.getTokenType() == RegExpTT.COLON) { - builder.advanceLexer(); - if (!parsePattern(builder)) { - // TODO: no test coverage - patternExpected(builder); - } else { - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); - } - marker.done(RegExpElementTypes.GROUP); - } else { - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed options group"); - marker.done(RegExpElementTypes.SET_OPTIONS); - } - } else if (type == StringEscapesTokenTypes.INVALID_CHARACTER_ESCAPE_TOKEN) { - builder.error("Illegal/unsupported escape sequence"); - builder.advanceLexer(); - marker.done(RegExpElementTypes.CHAR); - } else if (RegExpTT.CHARACTERS.contains(type)) { - builder.advanceLexer(); - marker.done(RegExpElementTypes.CHAR); - } else if (RegExpTT.BOUNDARIES.contains(type)) { - builder.advanceLexer(); - marker.done(RegExpElementTypes.BOUNDARY); - } else if (type == RegExpTT.BACKREF) { - builder.advanceLexer(); - marker.done(RegExpElementTypes.BACKREF); - } else if (type == RegExpTT.PYTHON_NAMED_GROUP || type == RegExpTT.RUBY_NAMED_GROUP || type == RegExpTT.RUBY_QUOTED_NAMED_GROUP) { - builder.advanceLexer(); - checkMatches(builder, RegExpTT.NAME, "Group name expected"); - checkMatches(builder, type == RegExpTT.RUBY_QUOTED_NAMED_GROUP ? RegExpTT.QUOTE : RegExpTT.GT, "Unclosed group name"); - if (!parsePattern(builder)) { - patternExpected(builder); - } else { - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); - } - marker.done(RegExpElementTypes.GROUP); - } else if (type == RegExpTT.PYTHON_NAMED_GROUP_REF) { - builder.advanceLexer(); - checkMatches(builder, RegExpTT.NAME, "Group name expected"); - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group reference"); - marker.done(RegExpElementTypes.PY_NAMED_GROUP_REF); - } else if (type == RegExpTT.PYTHON_COND_REF) { - builder.advanceLexer(); - if (builder.getTokenType() == RegExpTT.NAME || builder.getTokenType() == RegExpTT.NUMBER) { - builder.advanceLexer(); - } - else { - builder.error("Group name or number expected"); - } - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group reference"); - if (!parseBranch(builder)) { - patternExpected(builder); - } else { - if (builder.getTokenType() == RegExpTT.UNION) { - builder.advanceLexer(); - if (!parseBranch(builder)) { - patternExpected(builder); - } - } - checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); - } - marker.done(RegExpElementTypes.PY_COND_REF); - } else if (type == RegExpTT.PROPERTY) { - parseProperty(builder); - marker.done(RegExpElementTypes.PROPERTY); - } else if (RegExpTT.SIMPLE_CLASSES.contains(type)) { - builder.advanceLexer(); - marker.done(RegExpElementTypes.SIMPLE_CLASS); - } else if (type == RegExpTT.CLASS_BEGIN) { - marker.drop(); - return parseClass(builder); + parseQuantifierType(builder); + marker.done(RegExpElementTypes.QUANTIFIER); + } + else { + if (!minOmitted) { + checkMatches(builder, RegExpTT.COMMA, "',' expected"); } - else if (type == RegExpTT.LBRACE && myAllowDanglingMetacharacters) { - builder.advanceLexer(); - marker.done(RegExpElementTypes.CHAR); + if (builder.getTokenType() == RegExpTT.RBRACE) { + builder.advanceLexer(); + parseQuantifierType(builder); + marker.done(RegExpElementTypes.QUANTIFIER); + } + else if (builder.getTokenType() == RegExpTT.NUMBER) { + builder.advanceLexer(); + checkMatches(builder, RegExpTT.RBRACE, "'}' expected"); + parseQuantifierType(builder); + marker.done(RegExpElementTypes.QUANTIFIER); } else { - marker.drop(); - return null; + builder.error("'}' or number expected"); + marker.done(RegExpElementTypes.QUANTIFIER); + return true; } - return marker; + } + } + else if (RegExpTT.QUANTIFIERS.contains(builder.getTokenType())) { + builder.advanceLexer(); + parseQuantifierType(builder); + marker.done(RegExpElementTypes.QUANTIFIER); + } + else { + marker.drop(); + return false; } - private static void parseProperty(PsiBuilder builder) { - checkMatches(builder, RegExpTT.PROPERTY, "'\\p' expected"); + return true; + } - checkMatches(builder, RegExpTT.LBRACE, "Character category expected"); - if (builder.getTokenType() == RegExpTT.NAME) { - builder.advanceLexer(); - } else if (builder.getTokenType() == RegExpTT.RBRACE) { - builder.error("Empty character family"); - } else { - builder.error("Character family name expected"); - builder.advanceLexer(); - } - checkMatches(builder, RegExpTT.RBRACE, "Unclosed character family"); + private static void parseQuantifierType(PsiBuilder builder) { + if (builder.getTokenType() == RegExpTT.PLUS) { + builder.advanceLexer(); + } + else if (builder.getTokenType() == RegExpTT.QUEST) { + builder.advanceLexer(); + } + else { + if (RegExpTT.QUANTIFIERS.contains(builder.getTokenType())) { + builder.error("Dangling metacharacter"); + } + } + } + + /** + * CLASS ::= "[" NEGATION DEFLIST "]" + * NEGATION ::= "^" | "" + * DEFLIST ::= INTERSECTION DEFLIST + * INTERSECTION ::= INTERSECTION "&&" CLASSDEF | CLASSDEF + * CLASSDEF ::= CLASS | SIMPLE_CLASSDEF | "" + * SIMPLE_CLASSDEF ::= CHARACTER | CHARACTER "-" CLASSDEF + */ + private PsiBuilder.Marker parseClass(PsiBuilder builder) { + final PsiBuilder.Marker marker = builder.mark(); + builder.advanceLexer(); + + if (builder.getTokenType() == RegExpTT.CARET) { + builder.advanceLexer(); } - private static void patternExpected(PsiBuilder builder) { - final IElementType token = builder.getTokenType(); - if (token == RegExpTT.GROUP_END) { - builder.error("Unmatched closing ')'"); - } else if (RegExpTT.QUANTIFIERS.contains(token)) { - builder.error("Dangling metacharacter"); - } else { - builder.error("Pattern expected"); - } + // DEFLIST + if (parseClassIntersection(builder)) { + while (RegExpTT.CHARACTERS2.contains(builder.getTokenType()) || + builder.getTokenType() == RegExpTT.CLASS_BEGIN || + builder.getTokenType() == RegExpTT.PROPERTY) { + parseClassIntersection(builder); + } } - protected static void checkMatches(final PsiBuilder builder, final IElementType token, final String message) { - if (builder.getTokenType() == token) { - builder.advanceLexer(); - } else { - builder.error(message); - } + checkMatches(builder, RegExpTT.CLASS_END, "Unclosed character class"); + marker.done(RegExpElementTypes.CLASS); + return marker; + } + + private boolean parseClassIntersection(PsiBuilder builder) { + PsiBuilder.Marker marker = builder.mark(); + + if (!parseClassdef(builder, false)) { + marker.drop(); + return false; } + while (RegExpTT.ANDAND == builder.getTokenType()) { + builder.advanceLexer(); + parseClassdef(builder, true); + marker.done(RegExpElementTypes.INTERSECTION); + marker = marker.precede(); + } + + marker.drop(); + return true; + } + + private boolean parseClassdef(PsiBuilder builder, boolean mayBeEmpty) { + final IElementType token = builder.getTokenType(); + if (token == RegExpTT.CLASS_BEGIN) { + parseClass(builder); + } + else if (RegExpTT.CHARACTERS2.contains(token)) { + parseSimpleClassdef(builder); + } + else if (token == RegExpTT.PROPERTY) { + parseProperty(builder); + } + else if (mayBeEmpty) { + // TODO: no test coverage + return true; + } + else { + return false; + } + return true; + } + + private void parseSimpleClassdef(PsiBuilder builder) { + assert RegExpTT.CHARACTERS2.contains(builder.getTokenType()); + + final PsiBuilder.Marker marker = builder.mark(); + makeChar(builder); + + IElementType t = builder.getTokenType(); + if (t == RegExpTT.MINUS) { + final PsiBuilder.Marker m = builder.mark(); + builder.advanceLexer(); + + t = builder.getTokenType(); + if (RegExpTT.CHARACTERS2.contains(t)) { + m.drop(); + makeChar(builder); + marker.done(RegExpElementTypes.CHAR_RANGE); + } + else { + marker.drop(); + m.done(t == RegExpTT.CHAR_CLASS ? RegExpElementTypes.SIMPLE_CLASS : RegExpElementTypes.CHAR); + + if (t == RegExpTT.CLASS_END) { // [a-] + return; + } + else if (t == RegExpTT.CLASS_BEGIN) { // [a-[b]] + if (parseClassdef(builder, false)) { + return; + } + } + builder.error("Illegal character range"); + } + } + else { + marker.drop(); + } + } + + private static void makeChar(PsiBuilder builder) { + final IElementType t = builder.getTokenType(); + PsiBuilder.Marker m = builder.mark(); + builder.advanceLexer(); + m.done(t == RegExpTT.CHAR_CLASS ? RegExpElementTypes.SIMPLE_CLASS : RegExpElementTypes.CHAR); + } + + /** + * GROUP ::= "(" PATTERN ")" | TERM + * TERM ::= "." | "$" | "^" | CHAR | CLASS | BACKREF + */ + @Nullable + private PsiBuilder.Marker parseGroup(PsiBuilder builder) { + final IElementType type = builder.getTokenType(); + + final PsiBuilder.Marker marker = builder.mark(); + + if (RegExpTT.GROUPS.contains(type)) { + builder.advanceLexer(); + if (!parsePattern(builder)) { + patternExpected(builder); + } + else { + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); + } + marker.done(RegExpElementTypes.GROUP); + } + else if (type == RegExpTT.SET_OPTIONS) { + builder.advanceLexer(); + + final PsiBuilder.Marker o = builder.mark(); + if (builder.getTokenType() == RegExpTT.OPTIONS_ON) { + builder.advanceLexer(); + } + if (builder.getTokenType() == RegExpTT.OPTIONS_OFF) { + builder.advanceLexer(); + } + o.done(RegExpElementTypes.OPTIONS); + + if (builder.getTokenType() == RegExpTT.COLON) { + builder.advanceLexer(); + if (!parsePattern(builder)) { + // TODO: no test coverage + patternExpected(builder); + } + else { + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); + } + marker.done(RegExpElementTypes.GROUP); + } + else { + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed options group"); + marker.done(RegExpElementTypes.SET_OPTIONS); + } + } + else if (type == StringEscapesTokenTypes.INVALID_CHARACTER_ESCAPE_TOKEN) { + builder.error("Illegal/unsupported escape sequence"); + builder.advanceLexer(); + marker.done(RegExpElementTypes.CHAR); + } + else if (RegExpTT.CHARACTERS.contains(type)) { + builder.advanceLexer(); + marker.done(RegExpElementTypes.CHAR); + } + else if (RegExpTT.BOUNDARIES.contains(type)) { + builder.advanceLexer(); + marker.done(RegExpElementTypes.BOUNDARY); + } + else if (type == RegExpTT.BACKREF) { + builder.advanceLexer(); + marker.done(RegExpElementTypes.BACKREF); + } + else if (type == RegExpTT.PYTHON_NAMED_GROUP || type == RegExpTT.RUBY_NAMED_GROUP || type == RegExpTT.RUBY_QUOTED_NAMED_GROUP) { + builder.advanceLexer(); + checkMatches(builder, RegExpTT.NAME, "Group name expected"); + checkMatches(builder, type == RegExpTT.RUBY_QUOTED_NAMED_GROUP ? RegExpTT.QUOTE : RegExpTT.GT, "Unclosed group name"); + if (!parsePattern(builder)) { + patternExpected(builder); + } + else { + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); + } + marker.done(RegExpElementTypes.GROUP); + } + else if (type == RegExpTT.PYTHON_NAMED_GROUP_REF) { + builder.advanceLexer(); + checkMatches(builder, RegExpTT.NAME, "Group name expected"); + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group reference"); + marker.done(RegExpElementTypes.PY_NAMED_GROUP_REF); + } + else if (type == RegExpTT.PYTHON_COND_REF) { + builder.advanceLexer(); + if (builder.getTokenType() == RegExpTT.NAME || builder.getTokenType() == RegExpTT.NUMBER) { + builder.advanceLexer(); + } + else { + builder.error("Group name or number expected"); + } + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group reference"); + if (!parseBranch(builder)) { + patternExpected(builder); + } + else { + if (builder.getTokenType() == RegExpTT.UNION) { + builder.advanceLexer(); + if (!parseBranch(builder)) { + patternExpected(builder); + } + } + checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group"); + } + marker.done(RegExpElementTypes.PY_COND_REF); + } + else if (type == RegExpTT.PROPERTY) { + parseProperty(builder); + marker.done(RegExpElementTypes.PROPERTY); + } + else if (RegExpTT.SIMPLE_CLASSES.contains(type)) { + builder.advanceLexer(); + marker.done(RegExpElementTypes.SIMPLE_CLASS); + } + else if (type == RegExpTT.CLASS_BEGIN) { + marker.drop(); + return parseClass(builder); + } + else if (type == RegExpTT.LBRACE && myAllowDanglingMetacharacters) { + builder.advanceLexer(); + marker.done(RegExpElementTypes.CHAR); + } + else { + marker.drop(); + return null; + } + return marker; + } + + private static void parseProperty(PsiBuilder builder) { + checkMatches(builder, RegExpTT.PROPERTY, "'\\p' expected"); + + checkMatches(builder, RegExpTT.LBRACE, "Character category expected"); + if (builder.getTokenType() == RegExpTT.NAME) { + builder.advanceLexer(); + } + else if (builder.getTokenType() == RegExpTT.RBRACE) { + builder.error("Empty character family"); + } + else { + builder.error("Character family name expected"); + builder.advanceLexer(); + } + checkMatches(builder, RegExpTT.RBRACE, "Unclosed character family"); + } + + private static void patternExpected(PsiBuilder builder) { + final IElementType token = builder.getTokenType(); + if (token == RegExpTT.GROUP_END) { + builder.error("Unmatched closing ')'"); + } + else if (RegExpTT.QUANTIFIERS.contains(token)) { + builder.error("Dangling metacharacter"); + } + else { + builder.error("Pattern expected"); + } + } + + protected static void checkMatches(final PsiBuilder builder, final IElementType token, final String message) { + if (builder.getTokenType() == token) { + builder.advanceLexer(); + } + else { + builder.error(message); + } + } }