// Copyright 2006-2012 AdvancedTools. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.advancedtools.cpp.makefile; import com.advancedtools.cpp.CppSupportLoader; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.lang.ASTNode; import com.intellij.lang.ParserDefinition; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.lexer.Lexer; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; /** * @author maxim * Date: 2/3/12 * Time: 1:28 PM */ public class MakefileParserDefinition implements ParserDefinition { @NotNull public Lexer createLexer(Project project) { return new MakefileParsingLexer(); } public PsiParser createParser(Project project) { return new MakefileParser(); } public IFileElementType getFileNodeType() { return MakefileTokenTypes.MAKE_FILE; } @NotNull public TokenSet getWhitespaceTokens() { return MakefileTokenTypes.WHITE_SPACES; } @NotNull public TokenSet getCommentTokens() { return MakefileTokenTypes.COMMENTS; } // IDEA8 @NotNull public TokenSet getStringLiteralElements() { return MakefileTokenTypes.LITERALS; } @NotNull public PsiElement createElement(ASTNode astNode) { if (MakefileParser.shouldProduceDefinition(astNode.getElementType())) { return new MakefileNamedElement(astNode); } return new MakefilePsiElement(astNode); } public PsiFile createFile(FileViewProvider fileViewProvider) { return new MakeFile(fileViewProvider); } public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode astNode, ASTNode astNode1) { return SpaceRequirements.MAY; } public static class MakefileParser implements PsiParser { @NotNull public ASTNode parse(IElementType iElementType, PsiBuilder psiBuilder) { final PsiBuilder.Marker marker = psiBuilder.mark(); PsiBuilder.Marker statement = psiBuilder.mark(); IElementType statementType = null; while(!psiBuilder.eof()) { final IElementType tokenType = psiBuilder.getTokenType(); if (tokenType == MakefileTokenTypes.SEMANTIC_WHITESPACE) { psiBuilder.advanceLexer(); statement.done(statementType != null ? statementType:MakefileTokenTypes.STATEMENT); statementType = null; statement = psiBuilder.mark(); continue; } else if (shouldProduceComposite(tokenType)) { PsiBuilder.Marker identifier = psiBuilder.mark(); psiBuilder.advanceLexer(); identifier.done(tokenType); continue; } else if (tokenType == MakefileTokenTypes.VAR_DEFINITION) { statementType = tokenType; } else if (tokenType == MakefileTokenTypes.IDENTIFIER_START) { readIdentifier(psiBuilder, MakefileTokenTypes.IDENTIFIER_END, MakefileTokenTypes.IDENTIFIER); } else if (tokenType == MakefileTokenTypes.TARGET_IDENTIFIER_START) { readIdentifier(psiBuilder, MakefileTokenTypes.TARGET_IDENTIFIER_END, MakefileTokenTypes.TARGET_IDENTIFIER); if (!psiBuilder.eof()) psiBuilder.advanceLexer(); statementType = MakefileTokenTypes.TARGET_DECLARATION; continue; } psiBuilder.advanceLexer(); } statement.done(MakefileTokenTypes.STATEMENT); marker.done(iElementType); return psiBuilder.getTreeBuilt(); } private static void readIdentifier(PsiBuilder psiBuilder, IElementType identifierEnd, IElementType identifierType) { PsiBuilder.Marker identifier = psiBuilder.mark(); psiBuilder.advanceLexer(); IElementType currentType; while(!psiBuilder.eof() && (currentType = psiBuilder.getTokenType()) != identifierEnd) { PsiBuilder.Marker marker = shouldProduceComposite(currentType) ? psiBuilder.mark() : null; psiBuilder.advanceLexer(); if (marker != null) marker.done(currentType); } identifier.done(identifierType); } public static boolean shouldProduceComposite(IElementType tokenType) { return tokenType == MakefileTokenTypes.IDENTIFIER || tokenType == MakefileTokenTypes.VAR_REFERENCE; } public static boolean shouldProduceDefinition(IElementType elementType) { return elementType == MakefileTokenTypes.TARGET_DECLARATION || elementType == MakefileTokenTypes.VAR_DEFINITION; } } static class MakefileParsingLexer extends MakefileLexer { private CharSequence sequence; private final int ON_COMMENT_OR_CONTINUE_STATEMENT = _MakefileLexer.CONTINUE + 1; private final int ON_SEMANTIC_LF = ON_COMMENT_OR_CONTINUE_STATEMENT + 1; boolean hasPreviousCommentOrContinue; boolean onSemanticLineFeed; public MakefileParsingLexer() { super(false); } public void advance() { if (!onSemanticLineFeed) { super.advance(); IElementType tokenType = getTokenType(); if (!hasPreviousCommentOrContinue && tokenType == MakefileTokenTypes.WHITE_SPACE) { if (sequence == null) sequence = getBufferSequence(); final int tokenEnd = getTokenEnd(); for(int i = getTokenStart(); i < tokenEnd; ++i) { if (sequence.charAt(i) == '\n') { onSemanticLineFeed = true; break; } } } hasPreviousCommentOrContinue = tokenType == MakefileTokenTypes.END_OF_LINE_COMMENT || tokenType == MakefileTokenTypes.CONTINUE_STATEMENT; } else { onSemanticLineFeed = false; hasPreviousCommentOrContinue = false; } } public IElementType getTokenType() { return onSemanticLineFeed ? MakefileTokenTypes.SEMANTIC_WHITESPACE : super.getTokenType(); } public int getTokenEnd() { return onSemanticLineFeed ? getTokenStart():super.getTokenEnd(); } public int getState() { if (onSemanticLineFeed) return ON_SEMANTIC_LF; if (hasPreviousCommentOrContinue) return ON_COMMENT_OR_CONTINUE_STATEMENT; return super.getState(); } } /** * @author maxim * Date: 2/7/12 * Time: 12:53 PM */ public static class MakeFile extends PsiFileBase { public MakeFile(@NotNull FileViewProvider fileViewProvider) { super(fileViewProvider, CppSupportLoader.MAKEFILE_LANGUAGE); } @NotNull public FileType getFileType() { return CppSupportLoader.MAKE_FILETYPE; } public void accept(@NotNull PsiElementVisitor psiElementVisitor) { psiElementVisitor.visitFile(this); } @Override public String toString() { return "MakeFile:" + getName(); } } }