/*
* 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.stmt;
import org.jetbrains.annotations.NotNull;
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.parser.decl.FieldOrPropertyParsing;
import consulo.csharp.lang.parser.exp.ExpressionParsing;
import consulo.csharp.lang.psi.CSharpSoftTokens;
import consulo.csharp.lang.psi.CSharpTokens;
import com.intellij.lang.LighterASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.tree.IElementType;
/**
* @author VISTALL
* @since 16.12.13.
*/
public class StatementParsing extends SharedParsingHelpers
{
private static final String AWAIT_KEYWORD = "await";
public static PsiBuilder.Marker parse(CSharpBuilderWrapper wrapper, ModifierSet set)
{
return parseStatement(wrapper, set);
}
private static PsiBuilder.Marker parseStatement(CSharpBuilderWrapper builder, ModifierSet set)
{
PsiBuilder.Marker marker = builder.mark();
builder.enableSoftKeyword(CSharpSoftTokens.YIELD_KEYWORD);
IElementType tokenType = builder.getTokenType();
builder.disableSoftKeyword(CSharpSoftTokens.YIELD_KEYWORD);
if(tokenType == LOCK_KEYWORD)
{
parseStatementWithParenthesesExpression(builder, marker, LOCK_STATEMENT, set);
}
else if(tokenType == BREAK_KEYWORD)
{
builder.advanceLexer();
expect(builder, SEMICOLON, "';' expected");
marker.done(BREAK_STATEMENT);
}
else if(tokenType == GOTO_KEYWORD)
{
builder.advanceLexer();
final IElementType nextTokenType = builder.getTokenType();
if(nextTokenType == CSharpTokens.CASE_KEYWORD)
{
builder.advanceLexer();
if(ExpressionParsing.parse(builder, set) == null)
{
builder.error("Expression expected");
}
}
else if(nextTokenType == CSharpTokens.DEFAULT_KEYWORD)
{
builder.advanceLexer();
}
else
{
doneOneElement(builder, CSharpTokens.IDENTIFIER, REFERENCE_EXPRESSION, "Identifier expected");
}
expect(builder, SEMICOLON, "';' expected");
marker.done(GOTO_STATEMENT);
}
else if(tokenType == CONTINUE_KEYWORD)
{
builder.advanceLexer();
expect(builder, SEMICOLON, "';' expected");
marker.done(CONTINUE_STATEMENT);
}
else if(tokenType == FOR_KEYWORD)
{
parseForStatement(builder, marker, set);
}
else if(tokenType == DO_KEYWORD)
{
parseDoWhileStatement(builder, marker, set);
}
else if(tokenType == RETURN_KEYWORD)
{
parseReturnStatement(builder, marker, set);
}
else if(tokenType == THROW_KEYWORD)
{
parseThrowStatement(builder, marker, set);
}
else if(tokenType == FOREACH_KEYWORD)
{
parseForeachStatement(builder, marker, set);
}
else if(tokenType == YIELD_KEYWORD)
{
parseYieldStatement(builder, marker, set);
}
else if(tokenType == TRY_KEYWORD)
{
parseTryStatement(builder, marker, set);
}
else if(tokenType == CATCH_KEYWORD)
{
parseCatchStatement(builder, marker, set);
}
else if(tokenType == FINALLY_KEYWORD)
{
parseFinallyStatement(builder, marker, set);
}
else if(tokenType == UNSAFE_KEYWORD)
{
parseUnsafeStatement(builder, marker, set);
}
else if(tokenType == IF_KEYWORD)
{
parseIfStatement(builder, marker, set);
}
else if(tokenType == LBRACE)
{
parseBlockStatement(builder, marker, set);
}
else if(tokenType == WHILE_KEYWORD)
{
parseStatementWithParenthesesExpression(builder, marker, WHILE_STATEMENT, set);
}
else if(tokenType == CHECKED_KEYWORD || tokenType == UNCHECKED_KEYWORD)
{
parseCheckedStatement(builder, marker, set);
}
else if(tokenType == USING_KEYWORD)
{
parseUsingOrFixed(builder, marker, USING_STATEMENT, set);
}
else if(tokenType == FIXED_KEYWORD)
{
parseUsingOrFixed(builder, marker, FIXED_STATEMENT, set);
}
else if(tokenType == CASE_KEYWORD || tokenType == DEFAULT_KEYWORD && builder.lookAhead(1) == COLON) // accept with colon only,
// default can be expression
{
parseSwitchLabel(builder, marker, tokenType == CASE_KEYWORD, set);
}
else if(tokenType == SWITCH_KEYWORD)
{
parseSwitchStatement(builder, marker, set);
}
else if(tokenType == CONST_KEYWORD)
{
PsiBuilder.Marker varMark = builder.mark();
builder.advanceLexer();
FieldOrPropertyParsing.parseFieldOrLocalVariableAtTypeWithDone(builder, varMark, LOCAL_VARIABLE, NONE, true, set);
marker.done(LOCAL_VARIABLE_DECLARATION_STATEMENT);
}
else if(tokenType == SEMICOLON)
{
builder.advanceLexer();
marker.done(EMPTY_STATEMENT);
}
else
{
if(tokenType == CSharpTokens.IDENTIFIER && builder.lookAhead(1) == COLON)
{
parseLabeledStatement(builder, marker, set);
return marker;
}
if(parseVariableOrExpression(builder, marker, set) == null)
{
return null;
}
}
return marker;
}
private static enum ParseVariableOrExpressionResult
{
VARIABLE,
EXPRESSION
}
@Nullable
private static ParseVariableOrExpressionResult parseVariableOrExpression(CSharpBuilderWrapper builder, @Nullable PsiBuilder.Marker someMarker, ModifierSet set)
{
LocalVarType localVarType = canParseAsVariable(builder, set);
// need for example remap global keyword to identifier when it try to parse
builder.remapBackIfSoft();
switch(localVarType)
{
case NONE:
PsiBuilder.Marker expressionMarker = ExpressionParsing.parse(builder, set);
if(expressionMarker == null)
{
if(someMarker != null)
{
someMarker.drop();
}
return null;
}
else
{
if(someMarker != null)
{
expect(builder, SEMICOLON, "';' expected");
}
}
if(someMarker != null)
{
someMarker.done(EXPRESSION_STATEMENT);
}
return ParseVariableOrExpressionResult.EXPRESSION;
case WITH_NAME:
PsiBuilder.Marker mark = builder.mark();
FieldOrPropertyParsing.parseFieldOrLocalVariableAtTypeWithDone(builder, mark, LOCAL_VARIABLE, VAR_SUPPORT, false, set);
if(someMarker != null)
{
expect(builder, SEMICOLON, "';' expected");
someMarker.done(LOCAL_VARIABLE_DECLARATION_STATEMENT);
}
return ParseVariableOrExpressionResult.VARIABLE;
case NO_NAME:
PsiBuilder.Marker newMarker = builder.mark();
TypeInfo typeInfo = parseType(builder, BRACKET_RETURN_BEFORE | VAR_SUPPORT);
assert typeInfo != null;
reportIdentifier(builder, NONE);
newMarker.done(LOCAL_VARIABLE);
if(someMarker != null)
{
expect(builder, SEMICOLON, "';' expected");
someMarker.done(LOCAL_VARIABLE_DECLARATION_STATEMENT);
}
return ParseVariableOrExpressionResult.VARIABLE;
default:
throw new UnsupportedOperationException();
}
}
enum LocalVarType
{
NONE,
WITH_NAME,
NO_NAME
}
@NotNull
private static LocalVarType canParseAsVariable(CSharpBuilderWrapper builder, ModifierSet set)
{
PsiBuilder.Marker newMarker = builder.mark();
try
{
int currentOffset = builder.getCurrentOffset();
TypeInfo typeInfo = parseType(builder, BRACKET_RETURN_BEFORE | VAR_SUPPORT);
if(typeInfo == null)
{
return LocalVarType.NONE;
}
if(set.contains(CSharpSoftTokens.ASYNC_KEYWORD))
{
int startOffset = ((LighterASTNode) typeInfo.marker).getStartOffset();
int endOffset = ((LighterASTNode) typeInfo.marker).getEndOffset();
CharSequence sequence = builder.getOriginalText().subSequence(startOffset, endOffset);
if(isAwait(sequence))
{
return LocalVarType.NONE;
}
}
IElementType tokenType = builder.getTokenType();
if(tokenType == LPAR)
{
return LocalVarType.NONE;
}
CharSequence sequence = builder.getOriginalText().subSequence(currentOffset, builder.getCurrentOffset());
if(tokenType == CSharpTokens.IDENTIFIER)
{
if(builder.lookAhead(1) == CSharpTokens.SEMICOLON || builder.lookAhead(1) == CSharpTokens.EQ)
{
return LocalVarType.WITH_NAME;
}
if(StringUtil.containsLineBreak(sequence))
{
return LocalVarType.NO_NAME;
}
return LocalVarType.WITH_NAME;
}
else
{
if(StringUtil.containsLineBreak(sequence))
{
return LocalVarType.NO_NAME;
}
}
return LocalVarType.NONE;
}
finally
{
newMarker.rollbackTo();
}
}
private static boolean isAwait(CharSequence sequence)
{
if(sequence.length() < AWAIT_KEYWORD.length())
{
return false;
}
// check for await
for(int i = 0; i < AWAIT_KEYWORD.length(); i++)
{
char expectedChar = AWAIT_KEYWORD.charAt(i);
char actualChar = sequence.charAt(i);
if(actualChar != expectedChar)
{
return false;
}
}
if(sequence.length() == AWAIT_KEYWORD.length())
{
return true;
}
for(int i = AWAIT_KEYWORD.length(); i < sequence.length(); i++)
{
char c = sequence.charAt(i);
if(!StringUtil.isWhiteSpace(c))
{
return false;
}
}
return true;
}
private static void parseTryStatement(@NotNull CSharpBuilderWrapper builder, final PsiBuilder.Marker marker, ModifierSet set)
{
builder.advanceLexer();
if(builder.getTokenType() == LBRACE)
{
parseStatement(builder, set);
}
else
{
builder.error("'{' expected");
}
boolean has = false;
while(builder.getTokenType() == CATCH_KEYWORD)
{
parseCatchStatement(builder, null, set);
has = true;
}
if(builder.getTokenType() == FINALLY_KEYWORD)
{
parseFinallyStatement(builder, null, set);
has = true;
}
if(!has)
{
builder.error("'catch' or 'finally' expected");
}
marker.done(TRY_STATEMENT);
}
private static void parseCatchStatement(@NotNull CSharpBuilderWrapper builder, @Nullable PsiBuilder.Marker marker, ModifierSet set)
{
PsiBuilder.Marker mark;
if(marker != null)
{
builder.error("'try' expected");
mark = marker;
}
else
{
mark = builder.mark();
}
builder.advanceLexer();
if(builder.getTokenType() == LPAR)
{
builder.advanceLexer();
PsiBuilder.Marker varMarker = builder.mark();
if(parseType(builder) == null)
{
builder.error("Type expected");
varMarker.drop();
}
else
{
if(builder.getTokenType() == CSharpTokens.IDENTIFIER)
{
doneIdentifier(builder, 0);
}
varMarker.done(LOCAL_VARIABLE);
}
expect(builder, RPAR, "')' expected");
}
builder.enableSoftKeyword(CSharpSoftTokens.WHEN_KEYWORD);
IElementType tokenType = builder.getTokenType();
builder.disableSoftKeyword(CSharpSoftTokens.WHEN_KEYWORD);
if(tokenType == CSharpSoftTokens.WHEN_KEYWORD)
{
builder.advanceLexer();
parseExpressionInParenth(builder, set);
}
if(builder.getTokenType() == LBRACE)
{
parseStatement(builder, set);
}
else
{
builder.error("'{' expected");
}
mark.done(CATCH_STATEMENT);
}
private static void parseFinallyStatement(@NotNull CSharpBuilderWrapper builder, @Nullable PsiBuilder.Marker marker, ModifierSet set)
{
PsiBuilder.Marker mark;
if(marker != null)
{
builder.error("'try' expected");
mark = marker;
}
else
{
mark = builder.mark();
}
builder.advanceLexer();
if(builder.getTokenType() == LBRACE)
{
parseStatement(builder, set);
}
else
{
builder.error("'{' expected");
}
mark.done(FINALLY_STATEMENT);
}
private static void parseUnsafeStatement(@NotNull CSharpBuilderWrapper builder, @NotNull PsiBuilder.Marker marker, ModifierSet set)
{
builder.advanceLexer();
if(builder.getTokenType() == LBRACE)
{
parseStatement(builder, set);
}
else
{
builder.error("'{' expected");
}
marker.done(UNSAFE_STATEMENT);
}
private static void parseLabeledStatement(@NotNull CSharpBuilderWrapper builder, final PsiBuilder.Marker marker, ModifierSet set)
{
builder.advanceLexer();
builder.advanceLexer();
boolean empty = true;
while(parseStatement(builder, set) != null)
{
empty = false;
}
if(empty)
{
builder.error("Expected statement");
}
marker.done(LABELED_STATEMENT);
}
private static void parseThrowStatement(@NotNull CSharpBuilderWrapper builder, final PsiBuilder.Marker marker, ModifierSet set)
{
builder.advanceLexer();
ExpressionParsing.parse(builder, set);
expect(builder, SEMICOLON, "';' expected");
marker.done(THROW_STATEMENT);
}
private static void parseForStatement(@NotNull CSharpBuilderWrapper builder, final PsiBuilder.Marker marker, ModifierSet modifierSet)
{
builder.advanceLexer();
if(expect(builder, LPAR, "'(' expected"))
{
if(builder.getTokenType() != SEMICOLON)
{
ParseVariableOrExpressionResult parseVariableOrExpressionResult = parseVariableOrExpression(builder, null, modifierSet);
if(parseVariableOrExpressionResult == ParseVariableOrExpressionResult.EXPRESSION)
{
while(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
PsiBuilder.Marker nextMarker = ExpressionParsing.parse(builder, modifierSet);
if(nextMarker == null)
{
builder.error("Expression expected");
}
}
}
}
expect(builder, CSharpTokens.SEMICOLON, "';' expected");
ExpressionParsing.parse(builder, modifierSet);
expect(builder, CSharpTokens.SEMICOLON, "';' expected");
ExpressionParsing.parse(builder, modifierSet);
while(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
ExpressionParsing.parse(builder, modifierSet);
}
expect(builder, RPAR, "')' expected");
}
if(parseStatement(builder, modifierSet) == null)
{
builder.error("Statement expected");
}
marker.done(FOR_STATEMENT);
}
private static void parseSwitchStatement(@NotNull CSharpBuilderWrapper builder, PsiBuilder.Marker marker, ModifierSet set)
{
builder.advanceLexer();
if(!parseExpressionInParenth(builder, set))
{
builder.error("Expression expected");
}
if(builder.getTokenType() == LBRACE)
{
parseStatement(builder, set);
}
else
{
builder.error("'{' expected");
}
marker.done(SWITCH_STATEMENT);
}
private static void parseSwitchLabel(@NotNull CSharpBuilderWrapper builder, PsiBuilder.Marker marker, boolean caseLabel, ModifierSet set)
{
builder.advanceLexer();
if(caseLabel)
{
if(ExpressionParsing.parse(builder, set) == null)
{
builder.error("Expression expected");
}
}
expect(builder, COLON, "':' expected");
marker.done(SWITCH_LABEL_STATEMENT);
}
private static void parseUsingOrFixed(@NotNull CSharpBuilderWrapper builder, final PsiBuilder.Marker marker, IElementType to, ModifierSet set)
{
builder.advanceLexer();
if(expect(builder, LPAR, "'(' expected"))
{
if(parseVariableOrExpression(builder, null, set) == null)
{
builder.error("Variable or expression expected");
}
expect(builder, RPAR, "')' expected");
}
if(parseStatement(builder, set) == null)
{
builder.error("Statement expected");
}
marker.done(to);
}
@NotNull
private static PsiBuilder.Marker parseIfStatement(final CSharpBuilderWrapper builder, final PsiBuilder.Marker mark, ModifierSet set)
{
builder.advanceLexer();
if(!parseExpressionInParenth(builder, set))
{
mark.done(IF_STATEMENT);
return mark;
}
PsiBuilder.Marker thenStatement = parseStatement(builder, set);
if(thenStatement == null)
{
builder.error("Expected statement");
mark.done(IF_STATEMENT);
return mark;
}
if(!expect(builder, ELSE_KEYWORD, null))
{
mark.done(IF_STATEMENT);
return mark;
}
PsiBuilder.Marker elseStatement = parseStatement(builder, set);
if(elseStatement == null)
{
builder.error("Expected statement");
}
mark.done(IF_STATEMENT);
return mark;
}
private static void parseForeachStatement(CSharpBuilderWrapper builder, PsiBuilder.Marker marker, ModifierSet set)
{
assert builder.getTokenType() == FOREACH_KEYWORD;
builder.advanceLexer();
if(expect(builder, LPAR, "'(' expected"))
{
PsiBuilder.Marker varMarker = builder.mark();
if(parseType(builder, VAR_SUPPORT) != null)
{
expectOrReportIdentifier(builder, 0);
}
else
{
builder.error("Type expected");
}
varMarker.done(LOCAL_VARIABLE);
if(expect(builder, IN_KEYWORD, "'in' expected"))
{
if(ExpressionParsing.parse(builder, set) == null)
{
builder.error("Expression expected");
}
}
expect(builder, RPAR, "')' expected");
}
if(parseStatement(builder, set) == null)
{
builder.error("Statement expected");
}
marker.done(FOREACH_STATEMENT);
}
private static PsiBuilder.Marker parseBlockStatement(CSharpBuilderWrapper builder, PsiBuilder.Marker marker, ModifierSet set)
{
if(builder.getTokenType() == LBRACE)
{
builder.advanceLexer();
while(!builder.eof())
{
if(builder.getTokenType() == RBRACE)
{
break;
}
else
{
PsiBuilder.Marker anotherMarker = parse(builder, set);
if(anotherMarker == null)
{
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
mark.error("Unexpected token");
}
}
}
expect(builder, RBRACE, "'}' expected");
marker.done(BLOCK_STATEMENT);
return marker;
}
else
{
builder.error("'{' expected");
return null;
}
}
private static boolean parseExpressionInParenth(final CSharpBuilderWrapper builder, ModifierSet set)
{
if(!expect(builder, LPAR, "'(' expected"))
{
return false;
}
final PsiBuilder.Marker beforeExpr = builder.mark();
final PsiBuilder.Marker expr = ExpressionParsing.parse(builder, set);
if(expr == null || builder.getTokenType() == SEMICOLON)
{
beforeExpr.rollbackTo();
builder.error("Expression expected");
if(builder.getTokenType() != RPAR)
{
return false;
}
}
else
{
beforeExpr.drop();
if(builder.getTokenType() != RPAR)
{
builder.error("')' expected");
return false;
}
}
builder.advanceLexer();
return true;
}
private static void parseYieldStatement(CSharpBuilderWrapper builder, PsiBuilder.Marker marker, ModifierSet set)
{
assert builder.getTokenType() == YIELD_KEYWORD;
builder.advanceLexer();
if(builder.getTokenType() == BREAK_KEYWORD)
{
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
mark.done(BREAK_STATEMENT);
}
else if(builder.getTokenType() == RETURN_KEYWORD)
{
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
ExpressionParsing.parse(builder, set);
mark.done(RETURN_STATEMENT);
}
else
{
PsiBuilder.Marker mark = builder.mark();
if(builder.getTokenType() != RBRACE && builder.getTokenType() != SEMICOLON)
{
builder.advanceLexer();
}
mark.error("'break' or 'return' expected");
}
expect(builder, SEMICOLON, "';' expected");
marker.done(YIELD_STATEMENT);
}
private static void parseReturnStatement(CSharpBuilderWrapper wrapper, PsiBuilder.Marker marker, ModifierSet set)
{
assert wrapper.getTokenType() == RETURN_KEYWORD;
wrapper.advanceLexer();
ExpressionParsing.parse(wrapper, set);
expect(wrapper, SEMICOLON, "';' expected");
marker.done(RETURN_STATEMENT);
}
private static void parseDoWhileStatement(CSharpBuilderWrapper wrapper, PsiBuilder.Marker marker, ModifierSet set)
{
wrapper.advanceLexer();
if(wrapper.getTokenType() == LBRACE)
{
parseStatement(wrapper, set);
}
else
{
wrapper.error("'{' expected");
}
if(expect(wrapper, WHILE_KEYWORD, "'while' expected"))
{
parseExpressionInParenth(wrapper, set);
expect(wrapper, SEMICOLON, null);
}
marker.done(DO_WHILE_STATEMENT);
}
private static void parseCheckedStatement(CSharpBuilderWrapper wrapper, PsiBuilder.Marker marker, ModifierSet set)
{
if(wrapper.lookAhead(1) == LPAR)
{
ExpressionParsing.parse(wrapper, set);
marker.done(EXPRESSION_STATEMENT);
}
else
{
wrapper.advanceLexer();
if(wrapper.getTokenType() == LBRACE)
{
parseStatement(wrapper, set);
}
else
{
wrapper.error("'{' expected");
}
marker.done(CHECKED_STATEMENT);
}
}
private static void parseStatementWithParenthesesExpression(CSharpBuilderWrapper wrapper, PsiBuilder.Marker marker, IElementType doneElement, ModifierSet set)
{
wrapper.advanceLexer();
parseExpressionInParenth(wrapper, set);
if(StatementParsing.parse(wrapper, set) == null)
{
wrapper.error("Statement expected");
}
marker.done(doneElement);
}
}