/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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 com.goide.parser;
import com.goide.GoParserDefinition;
import com.goide.GoTypes;
import com.intellij.lang.LighterASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.WhitespacesBinders;
import com.intellij.lang.impl.PsiBuilderAdapter;
import com.intellij.lang.parser.GeneratedParserUtilBase;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.IndexingDataKeys;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.List;
public class GoParserUtil extends GeneratedParserUtilBase {
private static final Key<TObjectIntHashMap<String>> MODES_KEY = Key.create("MODES_KEY");
@NotNull
private static TObjectIntHashMap<String> getParsingModes(@NotNull PsiBuilder builder_) {
TObjectIntHashMap<String> flags = builder_.getUserDataUnprotected(MODES_KEY);
if (flags == null) builder_.putUserDataUnprotected(MODES_KEY, flags = new TObjectIntHashMap<>());
return flags;
}
public static boolean consumeBlock(PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level) {
PsiFile file = builder_.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY);
VirtualFile data = file != null ? file.getUserData(IndexingDataKeys.VIRTUAL_FILE) : null;
if (data == null) return false;
int i = 0;
PsiBuilder.Marker m = builder_.mark();
do {
IElementType type = builder_.getTokenType();
if (type == GoTypes.TYPE_ && nextIdentifier(builder_)) { // don't count a.(type), only type <ident>
m.rollbackTo();
return false;
}
i += type == GoTypes.LBRACE ? 1 : type == GoTypes.RBRACE ? -1 : 0;
builder_.advanceLexer();
}
while (i > 0 && !builder_.eof());
boolean result = i == 0;
if (result) {
m.drop();
}
else {
m.rollbackTo();
}
return result;
}
private static boolean nextIdentifier(PsiBuilder builder_) {
IElementType e;
int i = 0;
//noinspection StatementWithEmptyBody
while ((e = builder_.rawLookup(++i)) == GoParserDefinition.WS || e == GoParserDefinition.NLS) {
}
return e == GoTypes.IDENTIFIER;
}
public static boolean emptyImportList(PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level) {
PsiBuilder.Marker marker = getCurrentMarker(builder_ instanceof PsiBuilderAdapter ? ((PsiBuilderAdapter)builder_).getDelegate() : builder_);
if (marker != null) {
marker.setCustomEdgeTokenBinders(WhitespacesBinders.GREEDY_LEFT_BINDER, null);
}
return true;
}
public static boolean isModeOn(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode) {
return getParsingModes(builder_).get(mode) > 0;
}
public static boolean withOn(PsiBuilder builder_, int level_, String mode, Parser parser) {
return withImpl(builder_, level_, mode, true, parser, parser);
}
public static boolean withOff(PsiBuilder builder_, int level_, Parser parser, String... modes) {
TObjectIntHashMap<String> map = getParsingModes(builder_);
TObjectIntHashMap<String> prev = new TObjectIntHashMap<>();
for (String mode : modes) {
int p = map.get(mode);
if (p > 0) {
map.put(mode, 0);
prev.put(mode, p);
}
}
boolean result = parser.parse(builder_, level_);
prev.forEachEntry((mode, p) -> {
map.put(mode, p);
return true;
});
return result;
}
private static boolean withImpl(PsiBuilder builder_, int level_, String mode, boolean onOff, Parser whenOn, Parser whenOff) {
TObjectIntHashMap<String> map = getParsingModes(builder_);
int prev = map.get(mode);
boolean change = ((prev & 1) == 0) == onOff;
if (change) map.put(mode, prev << 1 | (onOff ? 1 : 0));
boolean result = (change ? whenOn : whenOff).parse(builder_, level_);
if (change) map.put(mode, prev);
return result;
}
public static boolean isModeOff(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode) {
return getParsingModes(builder_).get(mode) == 0;
}
public static boolean prevIsType(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level) {
LighterASTNode marker = builder_.getLatestDoneMarker();
IElementType type = marker != null ? marker.getTokenType() : null;
return type == GoTypes.ARRAY_OR_SLICE_TYPE || type == GoTypes.MAP_TYPE || type == GoTypes.STRUCT_TYPE;
}
public static boolean keyOrValueExpression(@NotNull PsiBuilder builder_, int level) {
PsiBuilder.Marker m = enter_section_(builder_);
boolean r = GoParser.Expression(builder_, level + 1, -1);
if (!r) r = GoParser.LiteralValue(builder_, level + 1);
IElementType type = r && builder_.getTokenType() == GoTypes.COLON ? GoTypes.KEY : GoTypes.VALUE;
exit_section_(builder_, m, type, r);
return r;
}
public static boolean enterMode(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode) {
TObjectIntHashMap<String> flags = getParsingModes(builder_);
if (!flags.increment(mode)) flags.put(mode, 1);
return true;
}
private static boolean exitMode(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode, boolean safe) {
TObjectIntHashMap<String> flags = getParsingModes(builder_);
int count = flags.get(mode);
if (count == 1) flags.remove(mode);
else if (count > 1) flags.put(mode, count - 1);
else if (!safe) builder_.error("Could not exit inactive '" + mode + "' mode at offset " + builder_.getCurrentOffset());
return true;
}
public static boolean exitMode(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode) {
return exitMode(builder_, level,mode, false);
}
public static boolean exitModeSafe(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level, String mode) {
return exitMode(builder_, level,mode, true);
}
public static boolean isBuiltin(@NotNull PsiBuilder builder_, @SuppressWarnings("UnusedParameters") int level) {
LighterASTNode marker = builder_.getLatestDoneMarker();
if (marker == null) return false;
String text = String.valueOf(builder_.getOriginalText().subSequence(marker.getStartOffset(), marker.getEndOffset())).trim();
return "make".equals(text) || "new".equals(text);
}
@Nullable
private static PsiBuilder.Marker getCurrentMarker(@NotNull PsiBuilder builder_) {
try {
for (Field field : builder_.getClass().getDeclaredFields()) {
if ("MyList".equals(field.getType().getSimpleName())) {
field.setAccessible(true);
//noinspection unchecked
return ContainerUtil.getLastItem((List<PsiBuilder.Marker>)field.get(builder_));
}
}
}
catch (Exception ignored) {}
return null;
}
public static boolean nextTokenIsSmart(PsiBuilder builder, IElementType token) {
return nextTokenIsFast(builder, token) || ErrorState.get(builder).completionState != null;
}
public static boolean nextTokenIsSmart(PsiBuilder builder, IElementType... tokens) {
return nextTokenIsFast(builder, tokens) || ErrorState.get(builder).completionState != null;
}
}