/*
* 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;
import gnu.trove.THashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.csharp.lang.parser.exp.ExpressionParsing;
import consulo.csharp.lang.psi.CSharpElements;
import consulo.csharp.lang.psi.CSharpSoftTokens;
import consulo.csharp.lang.psi.CSharpStubElements;
import consulo.csharp.lang.psi.CSharpTokenSets;
import consulo.csharp.lang.psi.CSharpTokens;
import consulo.csharp.lang.psi.CSharpTokensImpl;
import consulo.csharp.module.extension.CSharpLanguageVersion;
import com.intellij.lang.LighterASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.WhitespacesAndCommentsBinder;
import com.intellij.lang.WhitespacesBinders;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.BitUtil;
import com.intellij.util.NotNullFunction;
/**
* @author VISTALL
* @since 28.11.13.
*/
public class SharedParsingHelpers implements CSharpTokenSets, CSharpTokens, CSharpElements
{
public static class DocWhitespacesAndCommentsBinder implements WhitespacesAndCommentsBinder
{
public static WhitespacesAndCommentsBinder INSTANCE = new DocWhitespacesAndCommentsBinder();
@Override
public int getEdgePosition(final List<IElementType> tokens, final boolean atStreamEdge, final TokenTextGetter getter)
{
if(tokens.isEmpty())
{
return 0;
}
for(int i = tokens.size() - 1; i >= 0; i--)
{
if(tokens.get(i) == CSharpTokensImpl.LINE_DOC_COMMENT)
{
return i;
}
}
return tokens.size();
}
}
public static final TokenSet ourSemicolonSet = TokenSet.create(CSharpTokens.SEMICOLON);
public static final int NONE = 0;
public static final int VAR_SUPPORT = 1 << 0;
public static final int STUB_SUPPORT = 1 << 1;
public static final int LT_GT_HARD_REQUIRE = 1 << 2;
public static final int BRACKET_RETURN_BEFORE = 1 << 3;
public static final int WITHOUT_NULLABLE = 1 << 4;
public static final int ALLOW_EMPTY_TYPE_ARGUMENTS = 1 << 5;
public static final int INSIDE_DOC = 1 << 6;
public static class TypeInfo
{
public IElementType nativeElementType;
public boolean isParameterized;
public boolean isNullable;
public boolean isArray;
public boolean isMultiArray;
public PsiBuilder.Marker marker;
}
protected static void reportErrorUntil(CSharpBuilderWrapper builder, String error, TokenSet originalSet, TokenSet softSet)
{
while(!builder.eof())
{
if(originalSet.contains(builder.getTokenType()))
{
break;
}
if(builder.getTokenType() == CSharpTokens.IDENTIFIER)
{
builder.enableSoftKeywords(softSet);
IElementType tokenType = builder.getTokenType();
builder.disableSoftKeywords(softSet);
if(softSet.contains(tokenType))
{
// remap
builder.remapBackIfSoft();
break;
}
}
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
mark.error(error);
}
}
protected static void advanceUnexpectedToken(@NotNull PsiBuilder builder)
{
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
mark.error("Unexpected token");
}
protected static void doneIdentifier(PsiBuilder builder, int flags)
{
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
mark.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.IDENTIFIER : CSharpElements.IDENTIFIER);
}
protected static void done(PsiBuilder.Marker marker, IElementType elementType)
{
marker.done(elementType);
if(CSharpStubElements.QUALIFIED_MEMBERS.contains(elementType))
{
marker.setCustomEdgeTokenBinders(DocWhitespacesAndCommentsBinder.INSTANCE, null);
}
}
protected static boolean parseTypeList(@NotNull CSharpBuilderWrapper builder, int flags)
{
return parseTypeList(builder, flags, TokenSet.EMPTY);
}
protected static boolean parseTypeList(@NotNull CSharpBuilderWrapper builder, int flags, @NotNull TokenSet nameStopperSet)
{
boolean empty = true;
while(!builder.eof())
{
TypeInfo marker = parseType(builder, flags, nameStopperSet);
if(marker == null)
{
if(!empty)
{
builder.error("Type expected");
}
break;
}
empty = false;
if(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
}
else
{
break;
}
}
return empty;
}
@Nullable
public static TypeInfo parseType(@NotNull CSharpBuilderWrapper builder)
{
return parseType(builder, NONE, TokenSet.EMPTY);
}
@Nullable
public static TypeInfo parseType(@NotNull CSharpBuilderWrapper builder, int flags)
{
return parseType(builder, flags, TokenSet.EMPTY);
}
@Nullable
public static TypeInfo parseType(@NotNull CSharpBuilderWrapper builder, int flags, @NotNull TokenSet nameStopperSet)
{
TypeInfo typeInfo = parseInnerType(builder, flags, nameStopperSet);
if(typeInfo == null)
{
return null;
}
PsiBuilder.Marker marker = typeInfo.marker;
while(builder.getTokenType() == MUL)
{
typeInfo = new TypeInfo();
marker = marker.precede();
builder.advanceLexer();
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.POINTER_TYPE : CSharpElements.POINTER_TYPE);
}
boolean alreadyNullable = false;
if(!BitUtil.isSet(flags, WITHOUT_NULLABLE))
{
if(builder.getTokenType() == QUEST)
{
typeInfo = new TypeInfo();
typeInfo.isNullable = true;
marker = marker.precede();
builder.advanceLexer();
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.NULLABLE_TYPE : CSharpElements.NULLABLE_TYPE);
alreadyNullable = true;
}
}
while(builder.getTokenType() == LBRACKET)
{
PsiBuilder.Marker newMarker = marker.precede();
if(builder.lookAhead(1) == COMMA)
{
builder.advanceLexer(); // advance [
boolean multi = false;
while(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
multi = true;
}
expect(builder, RBRACKET, "']' expected");
newMarker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ARRAY_TYPE : CSharpElements.ARRAY_TYPE);
typeInfo = new TypeInfo();
typeInfo.isArray = true;
typeInfo.isMultiArray = multi;
marker = newMarker;
continue;
}
if(builder.lookAhead(1) == RBRACKET)
{
builder.advanceLexer();
builder.advanceLexer();
newMarker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ARRAY_TYPE : CSharpElements.ARRAY_TYPE);
typeInfo = new TypeInfo();
typeInfo.isArray = true;
marker = newMarker;
}
else
{
if(BitUtil.isSet(flags, BRACKET_RETURN_BEFORE))
{
newMarker.drop();
typeInfo.marker = marker;
return typeInfo;
}
else
{
builder.advanceLexer(); // advance [
builder.error("']' expected");
typeInfo = new TypeInfo();
typeInfo.isArray = true;
newMarker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ARRAY_TYPE : CSharpElements.ARRAY_TYPE);
marker = newMarker;
}
}
}
while(builder.getTokenType() == MUL)
{
typeInfo = new TypeInfo();
marker = marker.precede();
builder.advanceLexer();
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.POINTER_TYPE : CSharpElements.POINTER_TYPE);
}
if(!typeInfo.isArray && !alreadyNullable)
{
if(!BitUtil.isSet(flags, WITHOUT_NULLABLE))
{
if(builder.getTokenType() == QUEST)
{
typeInfo = new TypeInfo();
typeInfo.isNullable = true;
marker = marker.precede();
builder.advanceLexer();
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.NULLABLE_TYPE : CSharpElements.NULLABLE_TYPE);
}
}
}
typeInfo.marker = marker;
return typeInfo;
}
private static TypeInfo parseInnerType(@NotNull CSharpBuilderWrapper builder, int flags, TokenSet nameStopperSet)
{
TypeInfo typeInfo = new TypeInfo();
PsiBuilder.Marker marker = builder.mark();
boolean varSupport = BitUtil.isSet(flags, VAR_SUPPORT) && builder.getVersion().isAtLeast(CSharpLanguageVersion._2_0);
if(varSupport)
{
builder.enableSoftKeyword(CSharpSoftTokens.VAR_KEYWORD);
}
IElementType tokenType = builder.getTokenType();
if(varSupport)
{
builder.disableSoftKeyword(CSharpSoftTokens.VAR_KEYWORD);
}
typeInfo.marker = marker;
if(CSharpTokenSets.NATIVE_TYPES.contains(tokenType))
{
builder.advanceLexer();
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.NATIVE_TYPE : CSharpElements.NATIVE_TYPE);
typeInfo.nativeElementType = tokenType;
}
else if(builder.getTokenType() == CSharpTokens.LPAR)
{
builder.advanceLexer();
if(builder.getTokenType() == CSharpTokens.RPAR)
{
builder.error("Expected type");
builder.advanceLexer();
}
else
{
int count = 0;
while(!builder.eof())
{
TypeInfo inner = parseType(builder, flags);
if(inner == null)
{
builder.error("Expected type");
}
else
{
PsiBuilder.Marker precede = inner.marker.precede();
if(builder.getTokenType() == CSharpTokens.IDENTIFIER)
{
doneIdentifier(builder, flags);
}
precede.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.TUPLE_VARIABLE : CSharpElements.TUPLE_VARIABLE);
count ++;
}
if(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
}
else
{
break;
}
}
if(count < 2)
{
builder.error("Expected comma");
}
expect(builder, CSharpTokens.RPAR, "Expected ')'");
}
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.TUPLE_TYPE : CSharpElements.TUPLE_TYPE);
}
else if(builder.getTokenType() == CSharpTokens.IDENTIFIER)
{
ExpressionParsing.ReferenceInfo referenceInfo = ExpressionParsing.parseQualifiedReference(builder, null, flags, nameStopperSet);
typeInfo.isParameterized = referenceInfo != null && referenceInfo.isParameterized;
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.USER_TYPE : CSharpElements.USER_TYPE);
}
else
{
marker.drop();
return null;
}
return typeInfo;
}
protected static boolean parseAttributeList(CSharpBuilderWrapper builder, ModifierSet set, int flags)
{
boolean empty = true;
while(builder.getTokenType() == LBRACKET)
{
empty = false;
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
builder.enableSoftKeywords(ATTRIBUTE_TARGETS);
IElementType tokenType = builder.getTokenType();
builder.disableSoftKeywords(ATTRIBUTE_TARGETS);
if(builder.lookAhead(1) != COLON)
{
builder.remapBackIfSoft();
}
else
{
if(!ATTRIBUTE_TARGETS.contains(tokenType))
{
builder.error("Wrong attribute target");
}
builder.advanceLexer(); // target type
builder.advanceLexer(); // colon
}
while(!builder.eof())
{
PsiBuilder.Marker attMark = parseAttribute(builder, set, flags);
if(attMark == null)
{
builder.error("Attribute name expected");
break;
}
if(builder.getTokenType() == COMMA)
{
builder.advanceLexer();
}
else if(builder.getTokenType() == RBRACKET)
{
break;
}
}
expect(builder, RBRACKET, "']' expected");
mark.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ATTRIBUTE_LIST : CSharpElements.ATTRIBUTE_LIST);
}
return empty;
}
private static PsiBuilder.Marker parseAttribute(CSharpBuilderWrapper builder, ModifierSet set, int flags)
{
PsiBuilder.Marker mark = builder.mark();
if(ExpressionParsing.parseQualifiedReference(builder, null, flags, TokenSet.EMPTY) == null)
{
mark.drop();
return null;
}
ExpressionParsing.parseArgumentList(builder, true, set);
mark.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ATTRIBUTE : CSharpElements.ATTRIBUTE);
return mark;
}
protected static Pair<PsiBuilder.Marker, ModifierSet> parseModifierList(CSharpBuilderWrapper builder, int flags)
{
PsiBuilder.Marker marker = builder.mark();
Set<IElementType> set = new THashSet<>();
while(!builder.eof())
{
if(MODIFIERS.contains(builder.getTokenType()))
{
set.add(builder.getTokenType());
builder.advanceLexer();
}
else
{
break;
}
}
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.MODIFIER_LIST : CSharpElements.MODIFIER_LIST);
return Pair.create(marker, ModifierSet.create(set));
}
protected static Pair<PsiBuilder.Marker, ModifierSet> parseModifierListWithAttributes(CSharpBuilderWrapper builder, int flags)
{
if(MODIFIERS.contains(builder.getTokenType()))
{
return parseModifierList(builder, flags);
}
else
{
Set<IElementType> set = new THashSet<>();
PsiBuilder.Marker marker = builder.mark();
if(!parseAttributeList(builder, ModifierSet.EMPTY, flags))
{
// FIXME [VISTALL] dummy
set.add(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.ATTRIBUTE : CSharpElements.ATTRIBUTE);
}
while(!builder.eof())
{
if(MODIFIERS.contains(builder.getTokenType()))
{
set.add(builder.getTokenType());
builder.advanceLexer();
}
else
{
break;
}
}
marker.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.MODIFIER_LIST : CSharpElements.MODIFIER_LIST);
return Pair.create(marker, ModifierSet.create(set));
}
}
@NotNull
protected static <T> Pair<PsiBuilder.Marker, T> parseWithSoftElements(NotNullFunction<CSharpBuilderWrapper, Pair<PsiBuilder.Marker, T>> func,
CSharpBuilderWrapper builderWrapper,
IElementType... softs)
{
return parseWithSoftElements(func, builderWrapper, TokenSet.create(softs));
}
@NotNull
protected static <T> Pair<PsiBuilder.Marker, T> parseWithSoftElements(NotNullFunction<CSharpBuilderWrapper, Pair<PsiBuilder.Marker, T>> func, CSharpBuilderWrapper builderWrapper, TokenSet softs)
{
builderWrapper.enableSoftKeywords(softs);
Pair<PsiBuilder.Marker, T> fun = func.fun(builderWrapper);
builderWrapper.disableSoftKeywords(softs);
return fun;
}
@Nullable
public static IElementType exprType(@Nullable final PsiBuilder.Marker marker)
{
return marker != null ? ((LighterASTNode) marker).getTokenType() : null;
}
public static boolean expect(PsiBuilder builder, IElementType elementType, String message)
{
if(builder.getTokenType() == elementType)
{
builder.advanceLexer();
return true;
}
else
{
if(message != null)
{
builder.error(message);
}
return false;
}
}
public static boolean expectOrReportIdentifier(PsiBuilder builder, int flags)
{
if(builder.getTokenType() == CSharpTokens.IDENTIFIER)
{
doneIdentifier(builder, flags);
return true;
}
else
{
reportIdentifier(builder, flags);
return false;
}
}
public static void reportIdentifier(PsiBuilder builder, int flags)
{
PsiBuilder.Marker mark = builder.mark();
builder.error("Expected identifier");
mark.done(BitUtil.isSet(flags, STUB_SUPPORT) ? CSharpStubElements.IDENTIFIER : CSharpElements.IDENTIFIER);
mark.setCustomEdgeTokenBinders(WhitespacesBinders.GREEDY_LEFT_BINDER, null);
}
protected static boolean expect(PsiBuilder builder, TokenSet tokenSet, String message)
{
if(tokenSet.contains(builder.getTokenType()))
{
builder.advanceLexer();
return true;
}
else
{
if(message != null)
{
builder.error(message);
}
return false;
}
}
public static void emptyElement(final PsiBuilder builder, final IElementType type)
{
builder.mark().done(type);
}
protected static boolean doneOneElement(PsiBuilder builder, IElementType elementType, IElementType to, String message)
{
PsiBuilder.Marker mark = builder.mark();
if(expect(builder, elementType, message))
{
mark.done(to);
return true;
}
else
{
mark.drop();
return false;
}
}
public static boolean expectGGLL(CSharpBuilderWrapper builder, IElementType elementType, String message)
{
if(builder.getTokenTypeGGLL() == elementType)
{
builder.advanceLexerGGLL();
return true;
}
else
{
if(message != null)
{
builder.error(message);
}
return false;
}
}
protected static boolean doneOneElementGGLL(CSharpBuilderWrapper builder, IElementType elementType, IElementType to, String message)
{
PsiBuilder.Marker mark = builder.mark();
if(expectGGLL(builder, elementType, message))
{
mark.done(to);
return true;
}
else
{
mark.drop();
return false;
}
}
}