/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.lang.parser.exp; import org.jetbrains.annotations.Nullable; import consulo.csharp.lang.parser.CSharpBuilderWrapper; import consulo.csharp.lang.parser.ModifierSet; import consulo.csharp.lang.parser.SharedParsingHelpers; import consulo.csharp.lang.psi.CSharpSoftTokens; import consulo.csharp.lang.psi.CSharpTokens; import com.intellij.lang.PsiBuilder; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; /** * @author VISTALL * @since 04.01.14. */ public class LinqParsing extends SharedParsingHelpers { public static final TokenSet LINQ_KEYWORDS = TokenSet.create(LET_KEYWORD, FROM_KEYWORD, SELECT_KEYWORD, GROUP_KEYWORD, BY_KEYWORD, INTO_KEYWORD, ORDERBY_KEYWORD, WHERE_KEYWORD, ASCENDING_KEYWORD, DESCENDING_KEYWORD, JOIN_KEYWORD, ON_KEYWORD, EQUALS_KEYWORD); public static PsiBuilder.Marker parseLinqExpression(final CSharpBuilderWrapper builder, ModifierSet set) { builder.enableSoftKeywords(LINQ_KEYWORDS); try { PsiBuilder.Marker linqExpressionMarker = builder.mark(); PsiBuilder.Marker marker = parseFromClause(builder, true, set); if(marker == null) { linqExpressionMarker.rollbackTo(); return null; } parseQueryBody(builder, set); linqExpressionMarker.done(LINQ_EXPRESSION); return linqExpressionMarker; } finally { builder.disableSoftKeywords(LINQ_KEYWORDS); } } private static void parseQueryBody(CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker queryBody = builder.mark(); while(!builder.eof()) { PsiBuilder.Marker qMarker = parseQueryBodyClause(builder, set); if(qMarker == null) { break; } } parseSelectOrGroupClause(builder, set); parseQueryContinuation(builder, set); queryBody.done(LINQ_QUERY_BODY); } private static void parseQueryContinuation(CSharpBuilderWrapper builder, ModifierSet set) { if(builder.getTokenType() == CSharpSoftTokens.INTO_KEYWORD) { PsiBuilder.Marker mark = builder.mark(); parseIntoClause(builder); parseQueryBody(builder, set); mark.done(LINQ_QUERY_CONTINUATION); } } @Nullable private static PsiBuilder.Marker parseFromClause(final CSharpBuilderWrapper builder, boolean rollback, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); PsiBuilder.Marker variableMarker = builder.mark(); if(canParseType(builder)) { parseType(builder, BRACKET_RETURN_BEFORE); } IElementType tokenType = builder.getTokenType(); if(tokenType == CSharpTokens.IDENTIFIER) { doneIdentifier(builder, NONE); } else { if(rollback) { variableMarker.rollbackTo(); mark.rollbackTo(); return null; } else { variableMarker.error("Identifier expected"); variableMarker = null; } } if(variableMarker != null) { variableMarker.done(LINQ_VARIABLE); } if(builder.getTokenType() == IN_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else { builder.error("'in' expected"); } mark.done(LINQ_FROM_CLAUSE); return mark; } private static boolean canParseType(CSharpBuilderWrapper builder) { TypeInfo typeInfo = parseType(builder, BRACKET_RETURN_BEFORE); if(typeInfo != null) { if(typeInfo.isParameterized || typeInfo.nativeElementType != null) { typeInfo.marker.rollbackTo(); return true; } if(builder.getTokenType() == CSharpTokens.IDENTIFIER) { typeInfo.marker.rollbackTo(); return true; } typeInfo.marker.rollbackTo(); return false; } else { return false; } } @Nullable private static PsiBuilder.Marker parseQueryBodyClause(CSharpBuilderWrapper builder, ModifierSet set) { IElementType tokenType = builder.getTokenType(); if(tokenType == FROM_KEYWORD) { return parseFromClause(builder, false, set); } else if(tokenType == WHERE_KEYWORD) { return parseWhereClause(builder, set); } else if(tokenType == ORDERBY_KEYWORD) { return parseOrderByClause(builder, set); } else if(tokenType == LET_KEYWORD) { return parseLetClause(builder, set); } else if(tokenType == JOIN_KEYWORD) { return parseJoinClause(builder, set); } return null; } @Nullable private static PsiBuilder.Marker parseJoinClause(final CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); PsiBuilder.Marker variableMarker = builder.mark(); if(canParseType(builder)) { parseType(builder, BRACKET_RETURN_BEFORE); } expectOrReportIdentifier(builder, 0); variableMarker.done(LINQ_VARIABLE); if(builder.getTokenType() == IN_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else { builder.error("'in' expected"); } if(builder.getTokenType() == ON_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else { builder.error("'on' expected"); } if(builder.getTokenType() == EQUALS_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else { builder.error("'equals' expected"); } parseIntoClause(builder); mark.done(LINQ_JOIN_CLAUSE); return mark; } private static void parseIntoClause(CSharpBuilderWrapper builder) { if(builder.getTokenType() == INTO_KEYWORD) { PsiBuilder.Marker tempMarker = builder.mark(); builder.advanceLexer(); PsiBuilder.Marker varMarker = builder.mark(); expectOrReportIdentifier(builder, NONE); varMarker.done(LINQ_VARIABLE); tempMarker.done(LINQ_INTRO_CLAUSE); } } private static PsiBuilder.Marker parseWhereClause(CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } mark.done(LINQ_WHERE_CLAUSE); return mark; } private static PsiBuilder.Marker parseLetClause(CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); if(builder.getTokenType() == CSharpTokens.IDENTIFIER) { PsiBuilder.Marker varMarker = builder.mark(); doneIdentifier(builder, NONE); if(expect(builder, EQ, "'=' expected")) { if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } varMarker.done(LINQ_VARIABLE); } else { builder.error("Identifier expected"); } mark.done(LINQ_LET_CLAUSE); return mark; } private static PsiBuilder.Marker parseOrderByClause(CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); while(!builder.eof()) { PsiBuilder.Marker subMarker = builder.mark(); if(ExpressionParsing.parse(builder, set) == null) { subMarker.drop(); subMarker = null; builder.error("Expression expected"); } if(builder.getTokenType() == ASCENDING_KEYWORD || builder.getTokenType() == DESCENDING_KEYWORD) { builder.advanceLexer(); } if(subMarker != null) { subMarker.done(LINQ_ORDERBY_ORDERING); } if(builder.getTokenType() == COMMA) { builder.advanceLexer(); } else { break; } } mark.done(LINQ_ORDERBY_CLAUSE); return mark; } private static void parseSelectOrGroupClause(CSharpBuilderWrapper builder, ModifierSet set) { PsiBuilder.Marker mark = builder.mark(); if(builder.getTokenType() == SELECT_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else if(builder.getTokenType() == GROUP_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } if(builder.getTokenType() == BY_KEYWORD) { builder.advanceLexer(); if(ExpressionParsing.parse(builder, set) == null) { builder.error("Expression expected"); } } else { builder.error("'by' expected"); } } else { mark.error("'select' or 'group' expected"); return; } mark.done(LINQ_SELECT_OR_GROUP_CLAUSE); } }