/*
* Copyright 2010 Jean-Paul Balabanian and Yngve Devik Hammersland
*
* This file is part of glsl4idea.
*
* Glsl4idea is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Glsl4idea is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with glsl4idea. If not, see <http://www.gnu.org/licenses/>.
*/
package glslplugin.lang.parser;
import com.intellij.lang.ForeignLeafType;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static glslplugin.lang.elements.GLSLElementTypes.*;
import static glslplugin.lang.elements.GLSLTokenTypes.*;
/**
* Base of GLSLParsing to clearly divide helper and parsing methods.
* This class contains the helper methods.
*
* @author Darkyen
*/
abstract class GLSLParsingBase {
/**
* The source of operatorTokens and the target for the AST nodes.
* Do not use directly, use specialized proxy functions below, which can handle preprocessor
* directives and macro replacements.
*/
protected final GLSLPsiBuilderAdapter b;
protected Map<String, List<ForeignLeafType>> definitions = new HashMap<String, List<ForeignLeafType>>();
protected Map<String, String> definitionTexts = new HashMap<String, String>();
GLSLParsingBase(PsiBuilder builder) {
b = new GLSLPsiBuilderAdapter(builder);
}
protected final class GLSLPsiBuilderAdapter extends MultiRemapPsiBuilderAdapter {
public GLSLPsiBuilderAdapter(PsiBuilder delegate) {
super(delegate);
}
@Override
public void advanceLexer() {
advanceLexer(true, true);
}
public void advanceLexer(boolean checkForPreprocessor, boolean remapTokens){
super.advanceLexer();
if(checkForPreprocessor) {
while (getTokenType() == PREPROCESSOR_BEGIN) {
parsePreprocessor();
}
}
if (remapTokens) {
advanceLexer_remapTokens();
}
}
public void advanceLexer_remapTokens(){
final String tokenText = getTokenText();
final String[] namesThroughWhichThisTokenWasRedefined = getNamesThroughWhichThisTokenWasRedefined();
for (String name : namesThroughWhichThisTokenWasRedefined) {
if (name != null && name.equals(tokenText)) {
// Happens for #define name .*name.*
// We must not replace it with itself, as it would lead to much tears (and probably is't up to spec anyway)
return;
}
}
final List<ForeignLeafType> definition = definitions.get(tokenText);
if (definition != null) {
Marker macro = mark();
remapCurrentTokenAdvanceLexer_remapTokens = false;
remapCurrentToken(definition, tokenText); //This will advance the lexer which will eat the (real or substituted) token and replace it with redefinition
remapCurrentTokenAdvanceLexer_remapTokens = true;
macro.done(new RedefinedTokenElementType(definitionTexts.get(tokenText)));
advanceLexer_remapTokens();
}
}
//Behold, the longest boolean on this hemisphere
//Used in advanceLexer_remapTokens to not remap immediately after advancing in remapCurrentToken
//That prevents two redefined tokens merging together (second becomes child of first)
//I know that it sounds complicated, but you will have to trust me.
private boolean remapCurrentTokenAdvanceLexer_remapTokens = true;
@Override
protected void remapCurrentTokenAdvanceLexer() {
advanceLexer(false, remapCurrentTokenAdvanceLexer_remapTokens);
}
}
//Utility code
/**
* Checks whether lexer is at the end of the file,
* complains about it if it is
* and closes all marks supplied (if eof).
*
* @return b.eof()
*/
protected final boolean eof(PsiBuilder.Marker... marksToClose) {
if (b.eof()) {
if (marksToClose.length > 0) {
for (PsiBuilder.Marker mark : marksToClose) {
mark.error("Premature end of file.");
}
} else {
b.error("Premature end of file.");
}
return true;
} else {
return false;
}
}
/**
* Verifies that the current token is of the given type, if not it will flag an error.
*
* @param type the expected token type.
* @param error an appropriate error message if any other token is found instead.
* @return indicates whether the match was successful or not.
*/
protected final boolean match(IElementType type, String error) {
final boolean matched = !b.eof() && b.getTokenType() == type;
if (matched) {
b.advanceLexer();
} else {
b.error(error);
}
return matched;
}
/**
* Consumes the next token if it is of the given types, otherwise it is ignored.
*
* @param types the expected token types.
* @return indicates whether the match was successful or not.
*/
protected final boolean tryMatch(IElementType... types) {
if (b.eof()) {
return false;
}
boolean match = false;
for (IElementType type : types) {
match |= b.getTokenType() == type;
}
if (match) {
b.advanceLexer();
}
return match;
}
/**
* Consumes the next token if it is contained in the given token set, otherwise it is ignored.
*
* @param types a token set containing the expected token types.
* @return indicates whether the match was successful or not.
*/
protected final boolean tryMatch(TokenSet types) {
boolean match = types.contains(b.getTokenType());
if (match) {
b.advanceLexer();
}
return match;
}
// Interface to implementation
protected abstract void parsePreprocessor();
}