/******************************************************************************* * Copyright (c) 2007, 2013 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Markus Schorn - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.core.parser.scanner; import java.util.ArrayList; import org.eclipse.cdt.core.dom.ast.IMacroBinding; import org.eclipse.cdt.core.parser.ISignificantMacros; import org.eclipse.cdt.core.parser.IToken; import org.eclipse.cdt.core.parser.OffsetLimitReachedException; import org.eclipse.cdt.core.parser.util.CharArrayObjectMap; import org.eclipse.cdt.core.parser.util.CharArraySet; /** * Represents part of the input to the preprocessor. This may be a file or the result of a macro expansion. * @since 5.0 */ final class ScannerContext { enum BranchKind { eIf, eElif, eElse, eEnd } enum CodeState { eActive, eParseInactive, eSkipInactive } final static class Conditional { private final CodeState fInitialState; private BranchKind fLast; private boolean fTakeElse= true; Conditional(CodeState state) { fInitialState= state; fLast= BranchKind.eIf; } boolean canHaveActiveBranch(boolean withinExpansion) { return fTakeElse && isActive(withinExpansion); } public boolean isActive(boolean withinExpansion) { return withinExpansion || fInitialState == CodeState.eActive; } } private CodeState fInactiveState= CodeState.eSkipInactive; private final int fDepth; private final ILocationCtx fLocationCtx; private final ScannerContext fParent; private final Lexer fLexer; private Token fTokens; private ArrayList<Conditional> fConditionals; private CodeState fCurrentState= CodeState.eActive; private IncludeSearchPathElement fFoundOnPath; private String fFoundViaDirective; private CharArraySet fInternalModifications; private CharArrayObjectMap<char[]> fSignificantMacros; private boolean fPragmaOnce; private int fLoadedVersionCount; /** * @param ctx * @param parent context to be used after this context is done. */ public ScannerContext(ILocationCtx ctx, ScannerContext parent, Lexer lexer) { fLocationCtx= ctx; fParent= parent; fLexer= lexer; fDepth = parent == null ? 0 : parent.fDepth + 1; } public ScannerContext(ILocationCtx ctx, ScannerContext parent, TokenList tokens) { this(ctx, parent, (Lexer) null); fTokens= tokens.first(); fInactiveState= CodeState.eSkipInactive; // no branches in result of macro expansion } public void setParseInactiveCode(boolean val) { fInactiveState= val ? CodeState.eParseInactive : CodeState.eSkipInactive; } /** * Returns the location context associated with this scanner context. */ public final ILocationCtx getLocationCtx() { return fLocationCtx; } /** * Returns the parent context which will be used after this context is finished. * May return <code>null</code>. */ public final ScannerContext getParent() { return fParent; } /** * Returns the depth of this context, equals the number of parents of this context. */ public final int getDepth() { return fDepth; } /** * Returns the lexer for this context. */ public final Lexer getLexer() { return fLexer; } /** * Needs to be called whenever we change over to another branch of conditional * compilation. Returns the conditional associated with the branch or <code>null</code>, * if the change is not legal at this point. */ public final Conditional newBranch(BranchKind branchKind, boolean withinExpansion) { if (fConditionals == null) { fConditionals= new ArrayList<Conditional>(); } Conditional result; // an if starts a new conditional construct if (branchKind == BranchKind.eIf) { fConditionals.add(result= new Conditional(fCurrentState)); return result; } // if we are not inside of an conditional there shouldn't be an #else, #elsif or #end final int pos= fConditionals.size() - 1; if (pos < 0) { return null; } // an #end just pops one construct and restores state if (branchKind == BranchKind.eEnd) { result= fConditionals.remove(pos); return result; } // #elif or #else cannot appear after another #else result= fConditionals.get(pos); if (result.fLast == BranchKind.eElse) return null; // store last kind result.fLast= branchKind; return result; } private void changeState(CodeState state, BranchKind kind, boolean withinExpansion, int offset) { if (!withinExpansion) { switch (state) { case eActive: if (fCurrentState == CodeState.eParseInactive) stopInactive(offset, kind); break; case eParseInactive: switch (fCurrentState) { case eActive: startInactive(offset, kind); break; case eParseInactive: separateInactive(offset, kind); break; case eSkipInactive: assert false; // in macro expansions, only. break; } break; case eSkipInactive: // no need to report this to the parser. break; } } fCurrentState= state; } private void startInactive(int offset, BranchKind kind) { final int nesting = getCodeBranchNesting(); final int oldNesting = getOldNestingLevel(kind, nesting); fTokens= new InactiveCodeToken(IToken.tINACTIVE_CODE_START, oldNesting, nesting, offset); } private void separateInactive(int offset, BranchKind kind) { final int nesting = getCodeBranchNesting(); final int oldNesting = getOldNestingLevel(kind, nesting); fTokens= new InactiveCodeToken(IToken.tINACTIVE_CODE_SEPARATOR, oldNesting, nesting, offset); } private int getOldNestingLevel(BranchKind kind, int nesting) { switch (kind) { case eIf: return nesting - 1; case eElif: case eElse: return nesting; case eEnd: return nesting + 1; } return nesting; } private void stopInactive(int offset, BranchKind kind) { final int nesting = getCodeBranchNesting(); final int oldNesting = getOldNestingLevel(kind, nesting); fTokens= new InactiveCodeToken(IToken.tINACTIVE_CODE_END, oldNesting, nesting, offset); } public CodeState getCodeState() { return fCurrentState; } /** * The preprocessor has to inform the context about the state of if- and elif- branches */ public CodeState setBranchState(Conditional cond, boolean isActive, boolean withinExpansion, int offset) { CodeState newState; if (isActive) { cond.fTakeElse= false; newState= cond.fInitialState; } else if (withinExpansion) { newState= CodeState.eSkipInactive; } else { newState= fInactiveState; } changeState(newState, cond.fLast, withinExpansion, offset); return newState; } /** * The preprocessor has to inform the context about the state of if- and elif- branches */ public CodeState setBranchEndState(Conditional cond, boolean withinExpansion, int offset) { // implicit state change CodeState newState = cond != null ? cond.fInitialState : CodeState.eActive; changeState(newState, BranchKind.eEnd, withinExpansion, offset); return newState; } /** * Returns the current token from this context. When called before calling nextPPToken() * a token of type {@link Lexer#tBEFORE_INPUT} will be returned. * @since 5.0 */ public final Token currentLexerToken() { if (fTokens != null) { return fTokens; } if (fLexer != null) { return fLexer.currentToken(); } return new Token(IToken.tEND_OF_INPUT, null, 0, 0); } /** * Returns the next token from this context. */ public Token nextPPToken() throws OffsetLimitReachedException { if (fTokens != null) { fTokens= (Token) fTokens.getNext(); return currentLexerToken(); } if (fLexer != null) { return fLexer.nextToken(); } return new Token(IToken.tEND_OF_INPUT, null, 0, 0); } /** * If this is a lexer based context the current line is consumed. * @see Lexer#consumeLine(int) */ public int consumeLine(int originPreprocessorDirective) throws OffsetLimitReachedException { if (fLexer != null) return fLexer.consumeLine(originPreprocessorDirective); return -1; } public void clearInactiveCodeMarkerToken() { fTokens= null; } /** * Returns the current nesting within code branches */ public int getCodeBranchNesting() { if (fConditionals == null) return 0; return fConditionals.size(); } /** * Returns the element of the include search path that was used to find this context, or <code>null</code> if not applicable. */ public IncludeSearchPathElement getFoundOnPath() { return fFoundOnPath; } /** * Returns the directive with which the this context was found, or <code>null</code> if not applicable. */ public String getFoundViaDirective() { return fFoundViaDirective; } /** * Returns the element of the include search path that was used to find this context, or <code>null</code> if not applicable. */ public void setFoundOnPath(IncludeSearchPathElement foundOnPath, String viaDirective) { fFoundOnPath= foundOnPath; fFoundViaDirective= viaDirective; } public void trackSignificantMacros() { fInternalModifications= new CharArraySet(5); fSignificantMacros= new CharArrayObjectMap<char[]>(5); } public void setPragmaOnce(boolean val) { fPragmaOnce= val; } public boolean isPragmaOnce() { return fPragmaOnce; } public void internalModification(char[] macroName) { final CharArraySet collector = findModificationCollector(); if (collector != null) collector.put(macroName); } private CharArraySet findModificationCollector() { ScannerContext ctx= this; do { final CharArraySet collector = ctx.fInternalModifications; if (collector != null) return collector; ctx= ctx.getParent(); } while (ctx != null); return null; } public void significantMacro(IMacroBinding macro) { if (fCurrentState == CodeState.eParseInactive) { // Macros in inactive code should not be considered significant to match behavior of indexer, // which doesn't parse inactive code (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=370146). return; } final char[] macroName= macro.getNameCharArray(); if (fInternalModifications != null && !fInternalModifications.containsKey(macroName)) { final char[] expansion = macro.getExpansion(); if (expansion != null) addSignificantMacro(macroName, SignificantMacros.shortenValue(expansion)); } } public void significantMacroDefined(char[] macroName) { if (fInternalModifications != null && !fInternalModifications.containsKey(macroName)) { addSignificantMacroDefined(macroName); } } private void addSignificantMacroDefined(char[] macroName) { char[] old= addSignificantMacro(macroName, SignificantMacros.DEFINED); if (old != null && old != SignificantMacros.DEFINED) { // Put back more detailed condition addSignificantMacro(macroName, old); } } public void significantMacroUndefined(char[] macroName) { if (fInternalModifications != null && !fInternalModifications.containsKey(macroName)) { addSignificantMacro(macroName, SignificantMacros.UNDEFINED); } } public CharArrayObjectMap<char[]> getSignificantMacros() { return fSignificantMacros; } public void propagateSignificantMacros() { if (fInternalModifications == null) return; if (fParent != null) { CharArraySet collector = fParent.findModificationCollector(); if (collector != null) { // Propagate internal modifications to first interested parent. collector.addAll(fInternalModifications); // Propagate significant macros to direct parent, if it is interested. if (collector == fParent.fInternalModifications) { for (int i= 0; i < fSignificantMacros.size(); i++) { final char[] name = fSignificantMacros.keyAt(i); if (!collector.containsKey(name)) { final char[] value= fSignificantMacros.getAt(i); if (value == SignificantMacros.DEFINED) { if (!collector.containsKey(name)) { fParent.addSignificantMacroDefined(name); } } else { fParent.addSignificantMacro(name, value); } } } } } } fInternalModifications= null; fSignificantMacros= null; } public void addSignificantMacros(ISignificantMacros sm) { if (fInternalModifications == null) return; sm.accept(new ISignificantMacros.IVisitor() { @Override public boolean visitValue(char[] macro, char[] value) { if (!fInternalModifications.containsKey(macro)) { addSignificantMacro(macro, value); } return true; } @Override public boolean visitUndefined(char[] macro) { if (!fInternalModifications.containsKey(macro)) { addSignificantMacro(macro, SignificantMacros.UNDEFINED); } return true; } @Override public boolean visitDefined(char[] macro) { if (!fInternalModifications.containsKey(macro)) { addSignificantMacro(macro, SignificantMacros.DEFINED); } return true; } }); } private char[] addSignificantMacro(char[] macro, char[] value) { if (CPreprocessor.isPreprocessorProvidedMacro(macro)) return null; return fSignificantMacros.put(macro, value); } public int getLoadedVersionCount() { return fLoadedVersionCount; } public void setLoadedVersionCount(int count) { fLoadedVersionCount= count; } @Override public String toString() { if (fParent == null) return fLocationCtx.toString(); return fParent.toString() + "\n" + fLocationCtx.toString(); //$NON-NLS-1$ } }