/* * 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.macro; import org.jetbrains.annotations.Nullable; import consulo.csharp.lang.parser.SharedParsingHelpers; import consulo.csharp.lang.psi.CSharpMacroElements; import consulo.csharp.lang.psi.CSharpMacroTokens; import com.intellij.lang.PsiBuilder; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; public class MacroExpressionParsing implements CSharpMacroTokens, CSharpMacroElements { private enum ExprType { CONDITIONAL_OR, CONDITIONAL_AND, UNARY } private static final TokenSet CONDITIONAL_OR_OPS = TokenSet.create(OROR); private static final TokenSet CONDITIONAL_AND_OPS = TokenSet.create(ANDAND); private static final TokenSet PREFIX_OPS = TokenSet.create(EXCL); @Nullable public static PsiBuilder.Marker parse(final PsiBuilder builder) { return parseExpression(builder, ExprType.CONDITIONAL_OR); } @Nullable private static PsiBuilder.Marker parseExpression(final PsiBuilder builder, final ExprType type) { switch(type) { case CONDITIONAL_OR: return parseBinary(builder, ExprType.CONDITIONAL_AND, CONDITIONAL_OR_OPS); case CONDITIONAL_AND: return parseBinary(builder, ExprType.UNARY, CONDITIONAL_AND_OPS); case UNARY: return parseUnary(builder); default: assert false : "Unexpected type: " + type; return null; } } @Nullable private static PsiBuilder.Marker parseUnary(final PsiBuilder builder) { final IElementType tokenType = builder.getTokenType(); if(PREFIX_OPS.contains(tokenType)) { final PsiBuilder.Marker unary = builder.mark(); builder.advanceLexer(); final PsiBuilder.Marker operand = parseUnary(builder); if(operand == null) { builder.error("Expression expected"); } unary.done(PREFIX_EXPRESSION); return unary; } else { return parsePrimary(builder); } } @Nullable private static PsiBuilder.Marker parseBinary(final PsiBuilder builder, final ExprType type, final TokenSet ops) { PsiBuilder.Marker result = parseExpression(builder, type); if(result == null) { return null; } int operandCount = 1; IElementType tokenType = builder.getTokenType(); IElementType currentExprTokenType = tokenType; while(true) { if(tokenType == null || !ops.contains(tokenType)) { break; } builder.advanceLexer(); final PsiBuilder.Marker right = parseExpression(builder, type); operandCount++; tokenType = builder.getTokenType(); if(tokenType == null || !ops.contains(tokenType) || tokenType != currentExprTokenType || right == null) { // save result = result.precede(); if(right == null) { builder.error("Expression expected"); } result.done(operandCount > 2 ? POLYADIC_EXPRESSION : BINARY_EXPRESSION); if(right == null) { break; } currentExprTokenType = tokenType; operandCount = 1; } } return result; } @Nullable private static PsiBuilder.Marker parsePrimary(final PsiBuilder builder) { PsiBuilder.Marker startMarker = builder.mark(); PsiBuilder.Marker expr = parsePrimaryExpressionStart(builder); if(expr == null) { startMarker.drop(); return null; } startMarker.drop(); return expr; } @Nullable private static PsiBuilder.Marker parsePrimaryExpressionStart(final PsiBuilder builder) { IElementType tokenType = builder.getTokenType(); if(tokenType == IDENTIFIER) { PsiBuilder.Marker refExpr = builder.mark(); builder.advanceLexer(); refExpr.done(REFERENCE_EXPRESSION); return refExpr; } if(tokenType == LPAR) { final PsiBuilder.Marker parenth = builder.mark(); builder.advanceLexer(); final PsiBuilder.Marker inner = parse(builder); if(inner == null) { builder.error("Expression expected"); } if(!SharedParsingHelpers.expect(builder, RPAR, null)) { if(inner != null) { builder.error("')' expected"); } } parenth.done(PARENTHESES_EXPRESSION); return parenth; } return null; } }