/*******************************************************************************
 * Copyright (c) 2004, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Anton Leherbauer (Wind River Systems)
 *     Markus Schorn (Wind River Systems)
 *     Sergey Prigogin (Google)
 *     Marc-Andre Laperle (Ericsson)
 *     Richard Eames
 *******************************************************************************/
package org.eclipse.cdt.internal.core.parser.scanner;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IFileNomination;
import org.eclipse.cdt.core.dom.ast.IMacroBinding;
import org.eclipse.cdt.core.dom.parser.IScannerExtensionConfiguration;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.parser.AbstractParserLogService;
import org.eclipse.cdt.core.parser.EndOfFileException;
import org.eclipse.cdt.core.parser.ExtendedScannerInfo;
import org.eclipse.cdt.core.parser.FileContent;
import org.eclipse.cdt.core.parser.GCCKeywords;
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IMacro;
import org.eclipse.cdt.core.parser.IParserLogService;
import org.eclipse.cdt.core.parser.IPreprocessorDirective;
import org.eclipse.cdt.core.parser.IProblem;
import org.eclipse.cdt.core.parser.IScanner;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.parser.ISignificantMacros;
import org.eclipse.cdt.core.parser.IToken;
import org.eclipse.cdt.core.parser.IncludeExportPatterns;
import org.eclipse.cdt.core.parser.IncludeFileContentProvider;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.OffsetLimitReachedException;
import org.eclipse.cdt.core.parser.ParseError;
import org.eclipse.cdt.core.parser.ParserLanguage;
import org.eclipse.cdt.core.parser.util.CharArrayIntMap;
import org.eclipse.cdt.core.parser.util.CharArrayMap;
import org.eclipse.cdt.core.parser.util.CharArrayObjectMap;
import org.eclipse.cdt.core.parser.util.CharArraySet;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.dom.IIncludeFileResolutionHeuristics;
import org.eclipse.cdt.internal.core.parser.EmptyFilesProvider;
import org.eclipse.cdt.internal.core.parser.IMacroDictionary;
import org.eclipse.cdt.internal.core.parser.scanner.ExpressionEvaluator.EvalException;
import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent.FileVersion;
import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent.InclusionKind;
import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContentProvider.DependsOnOutdatedFileException;
import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions;
import org.eclipse.cdt.internal.core.parser.scanner.MacroDefinitionParser.InvalidMacroDefinitionException;
import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.BranchKind;
import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.CodeState;
import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.Conditional;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.osgi.framework.Version;

/**
 * C-Preprocessor providing tokens for the parsers. The class should not be used directly,
 * rather than that you should be using the {@link IScanner} interface.
 * @since 5.0
 */
public class CPreprocessor implements ILexerLog, IScanner, IAdaptable {
	public static final String PROP_VALUE = "CPreprocessor"; //$NON-NLS-1$

	public static final int tDEFINED = IToken.FIRST_RESERVED_PREPROCESSOR;
	public static final int tEXPANDED_IDENTIFIER = IToken.FIRST_RESERVED_PREPROCESSOR + 1;
	public static final int tSCOPE_MARKER = IToken.FIRST_RESERVED_PREPROCESSOR + 2;
	public static final int tSPACE = IToken.FIRST_RESERVED_PREPROCESSOR + 3;
	public static final int tNOSPACE = IToken.FIRST_RESERVED_PREPROCESSOR + 4;
	public static final int tMACRO_PARAMETER = IToken.FIRST_RESERVED_PREPROCESSOR + 5;
	public static final int t__HAS_FEATURE = IToken.FIRST_RESERVED_PREPROCESSOR + 6;

	private static final int ORIGIN_PREPROCESSOR_DIRECTIVE = OffsetLimitReachedException.ORIGIN_PREPROCESSOR_DIRECTIVE;
	private static final int ORIGIN_INACTIVE_CODE = OffsetLimitReachedException.ORIGIN_INACTIVE_CODE;

	private static final char[] ONE = "1".toCharArray(); //$NON-NLS-1$

	// Standard built-ins
	private static final ObjectStyleMacro __CDT_PARSER__ = new ObjectStyleMacro("__CDT_PARSER__".toCharArray(), //$NON-NLS-1$
			Integer.toString(getCDTVersion()).toCharArray());
	private static final ObjectStyleMacro __cplusplus = new ObjectStyleMacro("__cplusplus".toCharArray(), //$NON-NLS-1$
			"201103L".toCharArray()); //$NON-NLS-1$
	private static final ObjectStyleMacro __STDC__ = new ObjectStyleMacro("__STDC__".toCharArray(), ONE); //$NON-NLS-1$
	private static final ObjectStyleMacro __STDC_HOSTED__ = new ObjectStyleMacro("__STDC_HOSTED__".toCharArray(), ONE); //$NON-NLS-1$
	private static final ObjectStyleMacro __STDC_VERSION__ = new ObjectStyleMacro("__STDC_VERSION__".toCharArray(), //$NON-NLS-1$
			"199901L".toCharArray()); //$NON-NLS-1$

	private static final DynamicMacro __FILE__ = new FileMacro("__FILE__".toCharArray()); //$NON-NLS-1$
	private static final DynamicMacro __DATE__ = new DateMacro("__DATE__".toCharArray()); //$NON-NLS-1$
	private static final DynamicMacro __TIME__ = new TimeMacro("__TIME__".toCharArray()); //$NON-NLS-1$
	private static final DynamicMacro __LINE__ = new LineMacro("__LINE__".toCharArray()); //$NON-NLS-1$
	private static final char[] __COUNTER__ = "__COUNTER__".toCharArray(); //$NON-NLS-1$
	private static final char[] ONCE = "once".toCharArray(); //$NON-NLS-1$

	static final int NO_EXPANSION = 0x01;
	// Set in contexts where preprocessor intrinsics such as 'defined'
	// or '__has_feature' need to be recognized.
	static final int PROTECT_INTRINSICS = 0x02;
	static final int STOP_AT_NL = 0x04;
	static final int CHECK_NUMBERS = 0x08;
	static final int REPORT_SIGNIFICANT_MACROS = 0x10;
	static final int IGNORE_UNDEFINED_SIGNIFICANT_MACROS = 0x20;

	private static final int MAX_INCLUSION_DEPTH = 200;

	private static final String TRACE_NO_GUARD = CCorePlugin.PLUGIN_ID + "/debug/scanner/missingIncludeGuards"; //$NON-NLS-1$

	/**
	 * Returns an integer, suitable for use as a macro value, representing the current
	 * version of the CDT feature, composited into a single number.
	 * For example, version 9.2.1 would be composited into 90201.
	 * Used as the value of the __CDT_PARSER__ macro.
	 */
	public static int getCDTVersion() {
		Version version = CCorePlugin.getCDTFeatureVersion();
		if (version != null) {
			int major = version.getMajor();
			int minor = version.getMinor();
			int micro = version.getMicro();
			int composite = major * 10000 + minor * 100 + micro;
			return composite;
		}
		// Fall back to the old approach of defining __CDT_PARSER__ as 1.
		return 1;
	}

	private final class MacroDictionary implements IMacroDictionary, ISignificantMacros.IVisitor {
		@Override
		public boolean satisfies(ISignificantMacros significantMacros) {
			return significantMacros.accept(this);
		}

		@Override
		public boolean visitDefined(char[] macro) {
			return isDefined(macro);
		}

		@Override
		public boolean visitUndefined(char[] macro) {
			return !isDefined(macro);
		}

		@Override
		public boolean visitValue(char[] macro, char[] value) {
			PreprocessorMacro m = fMacroDictionary.get(macro);
			return m != null && CharArrayUtils.equals(SignificantMacros.shortenValue(m.getExpansion()), value);
		}

		private boolean isDefined(char[] macro) {
			return fMacroDictionary.containsKey(macro);
		}
	}

	private interface IIncludeFileTester<T> {
		T checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath);
	}

	final private IIncludeFileTester<InternalFileContent> createCodeReaderTester = new IIncludeFileTester<InternalFileContent>() {
		@Override
		public InternalFileContent checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath) {
			final InternalFileContent fc;
			IFileNomination once = fFileContentProvider.isIncludedWithPragmaOnceSemantics(path);
			if (once != null) {
				fc = new InternalFileContent(path, InclusionKind.SKIP_FILE);
			} else {
				fc = fFileContentProvider.getContentForInclusion(path, fMacroDictionaryFacade);
			}
			if (fc != null) {
				fc.setFoundByHeuristics(isHeuristicMatch);
				fc.setFoundOnPath(onPath);
			}
			return fc;
		}
	};

	private static class IncludeResolution {
		final String fLocation;
		final boolean fHeuristic;

		IncludeResolution(String location, boolean heusistic) {
			fLocation = location;
			fHeuristic = heusistic;
		}
	}

	final private IIncludeFileTester<IncludeResolution> createPathTester = new IIncludeFileTester<IncludeResolution>() {
		@Override
		public IncludeResolution checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath) {
			if (fFileContentProvider.getInclusionExists(path)) {
				return new IncludeResolution(path, isHeuristicMatch);
			}
			return null;
		}
	};

	private final class TokenSequence implements ITokenSequence {
		private final boolean fStopAtNewline;

		TokenSequence(boolean stopAtNewline) {
			fStopAtNewline = stopAtNewline;
		}

		@Override
		public Token nextToken() throws OffsetLimitReachedException {
			final Lexer lexer = fCurrentContext.getLexer();
			Token t = lexer.nextToken();
			if (t.getType() == IToken.tPOUND && lexer.currentTokenIsFirstOnLine()) {
				executeDirective(lexer, t.getOffset(), true);
				t = lexer.currentToken();
			}
			if (fStopAtNewline && t.getType() == Lexer.tNEWLINE)
				return new Token(IToken.tEND_OF_INPUT, null, 0, 0);

			return t;
		}

		@Override
		public int getLastEndOffset() {
			return fCurrentContext.getLexer().getLastEndOffset();
		}

		@Override
		public Token currentToken() {
			Token t = fCurrentContext.currentLexerToken();
			if (fStopAtNewline && t.getType() == Lexer.tNEWLINE)
				return new Token(IToken.tEND_OF_INPUT, null, 0, 0);

			return t;
		}
	}

	private Set<String> sSupportedFeatures;

	TokenSequence fInputToMacroExpansion = new TokenSequence(false);
	TokenSequence fLineInputToMacroExpansion = new TokenSequence(true);

	final private AbstractParserLogService fLog;
	final private InternalFileContentProvider fFileContentProvider;

	private final IIncludeFileResolutionHeuristics fIncludeFileResolutionHeuristics;
	private final ExpressionEvaluator fExpressionEvaluator;
	private final MacroDefinitionParser fMacroDefinitionParser;
	private final MacroExpander fMacroExpander;

	// configuration
	final private LexerOptions fLexOptions = new LexerOptions();
	final private char[] fAdditionalNumericLiteralSuffixes;
	final private CharArrayIntMap fKeywords;
	final private CharArrayIntMap fPPKeywords;
	private final IncludeSearchPath fIncludeSearchPath;
	private String[][] fPreIncludedFiles = null;

	private int fContentAssistLimit = -1;
	private boolean fHandledCompletion = false;
	private boolean fSplitShiftRightOperator = false;

	// State information
	private final CharArrayMap<PreprocessorMacro> fMacroDictionary = new CharArrayMap<>(512);
	private final IMacroDictionary fMacroDictionaryFacade = new MacroDictionary();
	private final LocationMap fLocationMap;
	private CharArraySet fPreventInclusion;
	private CharArraySet fImports;

	private final ScannerContext fRootContext;
	protected ScannerContext fCurrentContext;

	private boolean isCancelled;
	private boolean fIsFirstFetchToken = true;

	private Token fPrefetchedTokens;
	private Token fLastToken;

	private InternalFileContent fRootContent;
	private boolean fHandledEndOfTranslationUnit;

	// Detection of include guards used around an include directive
	private char[] fExternIncludeGuard;
	private Set<String> fTracedGuards;

	public CPreprocessor(FileContent fileContent, IScannerInfo info, ParserLanguage language, IParserLogService log,
			IScannerExtensionConfiguration configuration, IncludeFileContentProvider readerFactory) {
		Token.resetCounterFor(info);
		if (readerFactory instanceof InternalFileContentProvider) {
			fFileContentProvider = (InternalFileContentProvider) readerFactory;
		} else if (readerFactory == null) {
			fFileContentProvider = EmptyFilesProvider.getInstance();
		} else {
			throw new IllegalArgumentException("Illegal reader factory"); //$NON-NLS-1$
		}
		if (fileContent instanceof InternalFileContent) {
			fRootContent = (InternalFileContent) fileContent;
		} else {
			throw new IllegalArgumentException("Illegal file content object"); //$NON-NLS-1$
		}

		fLog = AbstractParserLogService.convert(log);
		fAdditionalNumericLiteralSuffixes = nonNull(configuration.supportAdditionalNumericLiteralSuffixes());
		fLexOptions.fSupportDollarInIdentifiers = configuration.support$InIdentifiers();
		fLexOptions.fSupportAtSignInIdentifiers = configuration.supportAtSignInIdentifiers();
		fLexOptions.fSupportMinAndMax = configuration.supportMinAndMaxOperators();
		fLexOptions.fSupportSlashPercentComments = configuration.supportSlashPercentComments();
		fLexOptions.fSupportUTFLiterals = configuration.supportUTFLiterals();
		fLexOptions.fSupportRawStringLiterals = configuration.supportRawStringLiterals();
		fLexOptions.fSupportUserDefinedLiterals = configuration.supportUserDefinedLiterals();
		fLexOptions.fSupportDigitSeparators = configuration.supportDigitSeparators();
		if (info instanceof ExtendedScannerInfo)
			fLexOptions.fIncludeExportPatterns = ((ExtendedScannerInfo) info).getIncludeExportPatterns();
		fLocationMap = new LocationMap(fLexOptions);
		fKeywords = new CharArrayIntMap(40, -1);
		fPPKeywords = new CharArrayIntMap(40, -1);
		configureKeywords(language, configuration);

		fExpressionEvaluator = new ExpressionEvaluator(this);
		fMacroDefinitionParser = new MacroDefinitionParser();
		fMacroExpander = new MacroExpander(this, fMacroDictionary, fLocationMap, fLexOptions);
		fIncludeFileResolutionHeuristics = fFileContentProvider.getIncludeHeuristics();

		String contextPath = fFileContentProvider.getContextPath();
		if (contextPath == null) {
			contextPath = fRootContent.getFileLocation();
		}
		fIncludeSearchPath = configureIncludeSearchPath(new File(contextPath).getParentFile(), info);
		setupMacroDictionary(configuration, info, language);

		ILocationCtx ctx = fLocationMap.pushTranslationUnit(fRootContent.getFileLocation(), fRootContent.getSource());
		Lexer lexer = new Lexer(fRootContent.getSource(), fLexOptions, this, this);
		fRootContext = fCurrentContext = new ScannerContext(ctx, null, lexer);
		if (info instanceof IExtendedScannerInfo) {
			final IExtendedScannerInfo einfo = (IExtendedScannerInfo) info;
			fPreIncludedFiles = new String[][] { einfo.getMacroFiles(), einfo.getIncludeFiles() };
		}
		fFileContentProvider.resetForTranslationUnit();
	}

	private char[] detectIncludeGuard(String filePath, AbstractCharArray source, ScannerContext ctx) {
		if (!fFileContentProvider.shouldIndexAllHeaderVersions(filePath)) {
			final char[] guard = IncludeGuardDetection.detectIncludeGuard(source, fLexOptions, fPPKeywords);
			if (guard != null) {
				IFileNomination nom = fLocationMap.reportPragmaOnceSemantics(ctx.getLocationCtx());
				fFileContentProvider.reportPragmaOnceSemantics(filePath, nom);
				ctx.internalModification(guard);
				ctx.setPragmaOnce(true);
				return guard;
			}
		}

		ctx.trackSignificantMacros();

		if (ctx != fRootContext) {
			if (fLog.isTracing(TRACE_NO_GUARD)) {
				if (fTracedGuards == null)
					fTracedGuards = new HashSet<>();
				if (fTracedGuards.add(filePath))
					fLog.traceLog(TRACE_NO_GUARD, "No include guard in " + filePath); //$NON-NLS-1$
			}
		}
		return null;
	}

	@Override
	public void setSplitShiftROperator(boolean val) {
		fSplitShiftRightOperator = val;
	}

	@Override
	public void setComputeImageLocations(boolean val) {
		fLexOptions.fCreateImageLocations = val;
	}

	@Override
	public void setTrackIncludeExport(IncludeExportPatterns patterns) {
		fLexOptions.fIncludeExportPatterns = patterns;
	}

	@Override
	public void setContentAssistMode(int offset) {
		fContentAssistLimit = offset;
		fRootContext.getLexer().setContentAssistMode(offset);
	}

	public boolean isContentAssistMode() {
		return fRootContext.getLexer().isContentAssistMode();
	}

	@Override
	public void setProcessInactiveCode(boolean val) {
		fRootContext.setParseInactiveCode(val);
	}

	public ITranslationUnit getTranslationUnit() {
		return fRootContent.getTranslationUnit();
	}

	public boolean isSource() {
		return fRootContent.isSource();
	}

	@Override
	public ILocationResolver getLocationResolver() {
		return fLocationMap;
	}

	/**
	 * @see IScannerExtensionConfiguration#supportAdditionalNumericLiteralSuffixes
	 * @since 5.10
	 */
	@Override
	public char[] getAdditionalNumericLiteralSuffixes() {
		return fAdditionalNumericLiteralSuffixes;
	}

	private void configureKeywords(ParserLanguage language, IScannerExtensionConfiguration configuration) {
		Keywords.addKeywordsPreprocessor(fPPKeywords);
		if (language == ParserLanguage.C) {
			Keywords.addKeywordsC(fKeywords);
		} else {
			Keywords.addKeywordsCpp(fKeywords);
		}
		CharArrayIntMap additionalKeywords = configuration.getAdditionalKeywords();
		if (additionalKeywords != null) {
			fKeywords.putAll(additionalKeywords);
		}
		additionalKeywords = configuration.getAdditionalPreprocessorKeywords();
		if (additionalKeywords != null) {
			fPPKeywords.putAll(additionalKeywords);
		}
	}

	protected String getCurrentFilename() {
		return fLocationMap.getCurrentFilePath();
	}

	private char[] nonNull(char[] array) {
		return array == null ? CharArrayUtils.EMPTY_CHAR_ARRAY : array;
	}

	/**
	 * Returns include search path for a given current directory and a IScannerInfo.
	 * @param directory the current directory
	 * @param info scanner information, or {@code null} if not available
	 * @return the include search path
	 */
	public static IncludeSearchPath configureIncludeSearchPath(File directory, IScannerInfo info) {
		boolean inhibitUseOfCurrentFileDirectory = false;
		List<IncludeSearchPathElement> elements = new ArrayList<>();

		if (info != null) {
			// Quote includes first
			if (info instanceof IExtendedScannerInfo) {
				final IExtendedScannerInfo einfo = (IExtendedScannerInfo) info;
				final String[] paths = einfo.getLocalIncludePath();
				if (paths != null) {
					for (String path : paths) {
						if ("-".equals(path)) { //$NON-NLS-1$
							inhibitUseOfCurrentFileDirectory = true;
						} else {
							elements.add(new IncludeSearchPathElement(makeAbsolute(directory, path), true));
						}
					}
				}
			}
			// Regular includes
			String[] paths = info.getIncludePaths();
			if (paths != null) {
				for (String path : paths) {
					if ("-".equals(path)) { //$NON-NLS-1$
						inhibitUseOfCurrentFileDirectory = true;
					} else {
						elements.add(new IncludeSearchPathElement(makeAbsolute(directory, path), false));
					}
				}
			}
		}
		return new IncludeSearchPath(elements, inhibitUseOfCurrentFileDirectory);
	}

	private static String makeAbsolute(File directory, String includePath) {
		if (directory == null || new File(includePath).isAbsolute()) {
			return includePath;
		}
		return ScannerUtility.createReconciledPath(directory.getAbsolutePath(), includePath);
	}

	private void setupMacroDictionary(IScannerExtensionConfiguration config, IScannerInfo info, ParserLanguage lang) {
		// Built-in macros
		fMacroDictionary.put(__CDT_PARSER__.getNameCharArray(), __CDT_PARSER__);
		fMacroDictionary.put(__STDC__.getNameCharArray(), __STDC__);
		fMacroDictionary.put(__FILE__.getNameCharArray(), __FILE__);
		fMacroDictionary.put(__DATE__.getNameCharArray(), __DATE__);
		fMacroDictionary.put(__TIME__.getNameCharArray(), __TIME__);
		fMacroDictionary.put(__LINE__.getNameCharArray(), __LINE__);
		fMacroDictionary.put(__COUNTER__, new CounterMacro(__COUNTER__));

		if (lang == ParserLanguage.CPP) {
			fMacroDictionary.put(__cplusplus.getNameCharArray(), __cplusplus);
		} else {
			fMacroDictionary.put(__STDC_HOSTED__.getNameCharArray(), __STDC_HOSTED__);
			fMacroDictionary.put(__STDC_VERSION__.getNameCharArray(), __STDC_VERSION__);
		}

		IMacro[] toAdd = config.getAdditionalMacros();
		if (toAdd != null) {
			for (final IMacro macro : toAdd) {
				addMacroDefinition(macro.getSignature(), macro.getExpansion());
			}
		}

		final Map<String, String> macroDict = info.getDefinedSymbols();
		if (macroDict != null) {
			for (Map.Entry<String, String> entry : macroDict.entrySet()) {
				final String key = entry.getKey();
				final String value = entry.getValue().trim();
				addMacroDefinition(key.toCharArray(), value.toCharArray());
			}
		}

		Collection<PreprocessorMacro> predefined = fMacroDictionary.values();
		for (PreprocessorMacro macro : predefined) {
			fLocationMap.registerPredefinedMacro(macro);
		}
	}

	private void beforeFirstFetchToken() {
		if (fPreIncludedFiles != null) {
			handlePreIncludedFiles();
			fPreIncludedFiles = null;
		}
		final String location = fLocationMap.getTranslationUnitPath();
		InternalFileContent content;
		try {
			content = fFileContentProvider.getContentForContextToHeaderGap(location, fMacroDictionaryFacade);
		} catch (DependsOnOutdatedFileException e) {
			// Abort the parser, handled by the abstract indexer task.
			throw new RuntimeException(e);
		}
		if (content != null && content.getKind() == InclusionKind.FOUND_IN_INDEX) {
			processInclusionFromIndex(0, content, false);
		}

		detectIncludeGuard(location, fRootContent.getSource(), fRootContext);
		fLocationMap.parsingFile(fFileContentProvider, fRootContent);
		fRootContent = null;
	}

	private void handlePreIncludedFiles() {
		final String[] imacro = fPreIncludedFiles[0];
		if (imacro != null && imacro.length > 0) {
			final char[] buffer = createSyntheticFile(imacro);
			ILocationCtx ctx = fLocationMap.pushPreInclusion(new CharArray(buffer), 0, true);
			fCurrentContext = new ScannerContext(ctx, fCurrentContext, new Lexer(buffer, fLexOptions, this, this));
			ScannerContext preCtx = fCurrentContext;
			try {
				while (internalFetchToken(preCtx, CHECK_NUMBERS, false).getType() != IToken.tEND_OF_INPUT) {
					// just eat the tokens
				}
				final ILocationCtx locationCtx = fCurrentContext.getLocationCtx();
				fLocationMap.popContext(locationCtx);
				fCurrentContext = fCurrentContext.getParent();
				assert fCurrentContext == fRootContext;
			} catch (OffsetLimitReachedException e) {
			}
		}
		final String[] include = fPreIncludedFiles[1];
		if (include != null && include.length > 0) {
			final char[] buffer = createSyntheticFile(include);
			ILocationCtx ctx = fLocationMap.pushPreInclusion(new CharArray(buffer), 0, false);
			fCurrentContext = new ScannerContext(ctx, fCurrentContext, new Lexer(buffer, fLexOptions, this, this));
		}
		fPreIncludedFiles = null;
	}

	private char[] createSyntheticFile(String[] files) {
		int totalLength = 0;
		final char[] instruction = "#include <".toCharArray(); //$NON-NLS-1$
		for (String file : files) {
			totalLength += instruction.length + 2 + file.length();
		}
		final char[] buffer = new char[totalLength];
		int pos = 0;
		for (String file : files) {
			final char[] fileName = file.toCharArray();
			System.arraycopy(instruction, 0, buffer, pos, instruction.length);
			pos += instruction.length;
			System.arraycopy(fileName, 0, buffer, pos, fileName.length);
			pos += fileName.length;
			buffer[pos++] = '>';
			buffer[pos++] = '\n';
		}
		return buffer;
	}

	public PreprocessorMacro addMacroDefinition(char[] key, char[] value) {
		final Lexer lex = new Lexer(key, fLexOptions, ILexerLog.NULL, null);
		try {
			PreprocessorMacro result = fMacroDefinitionParser.parseMacroDefinition(lex, ILexerLog.NULL, value);
			fLocationMap.registerPredefinedMacro(result);
			fMacroDictionary.put(result.getNameCharArray(), result);
			return result;
		} catch (Exception e) {
			fLog.traceLog("Invalid macro definition: '" + String.valueOf(key) + "'"); //$NON-NLS-1$//$NON-NLS-2$
			return null;
		}
	}

	@Override
	public Map<String, IMacroBinding> getMacroDefinitions() {
		Map<String, IMacroBinding> hashMap = new HashMap<>(fMacroDictionary.size());
		for (char[] key : fMacroDictionary.keys()) {
			hashMap.put(String.valueOf(key), fMacroDictionary.get(key));
		}
		return hashMap;
	}

	@Override
	public boolean isOnTopContext() {
		ScannerContext ctx = fCurrentContext;
		while (ctx != null && ctx.getLocationCtx() instanceof LocationCtxMacroExpansion) {
			ctx = ctx.getParent();
		}
		return ctx == fRootContext;
	}

	@Override
	public void cancel() {
		isCancelled = true;
	}

	/**
	 * Returns the next token from the preprocessor without concatenating string literals
	 * and also without splitting the shift-right operator.
	 */
	private Token fetchToken() throws OffsetLimitReachedException {
		if (fIsFirstFetchToken) {
			beforeFirstFetchToken();
			fIsFirstFetchToken = false;
		}
		Token t = fPrefetchedTokens;
		if (t != null) {
			fPrefetchedTokens = (Token) t.getNext();
			t.setNext(null);
			return t;
		}
		try {
			t = internalFetchToken(fRootContext,
					CHECK_NUMBERS | REPORT_SIGNIFICANT_MACROS | IGNORE_UNDEFINED_SIGNIFICANT_MACROS, false);
		} catch (OffsetLimitReachedException e) {
			fHandledCompletion = true;
			throw e;
		}
		final int offset = fLocationMap.getSequenceNumberForOffset(t.getOffset());
		final int endOffset = fLocationMap.getSequenceNumberForOffset(t.getEndOffset());
		t.setOffset(offset, endOffset);
		t.setNext(null);
		return t;
	}

	private void pushbackToken(Token t) {
		t.setNext(fPrefetchedTokens);
		fPrefetchedTokens = t;
	}

	/**
	 * Returns next token for the parser. String literals are not concatenated. When
	 * the end is reached tokens with type {@link IToken#tEND_OF_INPUT}.
	 * @throws OffsetLimitReachedException see {@link Lexer}.
	 */
	public IToken nextTokenRaw() throws OffsetLimitReachedException {
		if (isCancelled) {
			throw new ParseError(ParseError.ParseErrorKind.TIMEOUT_OR_CANCELLED);
		}

		Token t1 = fetchToken();
		switch (t1.getType()) {
		case IToken.tCOMPLETION:
			fHandledCompletion = true;
			break;
		case IToken.tEND_OF_INPUT:
			if (fContentAssistLimit >= 0) {
				int useType = fHandledCompletion ? IToken.tEOC : IToken.tCOMPLETION;
				int sequenceNumber = fLocationMap.getSequenceNumberForOffset(fContentAssistLimit);
				t1 = new Token(useType, null, sequenceNumber, sequenceNumber);
				fHandledCompletion = true;
			}
			break;
		case IToken.t_PRAGMA:
			handlePragmaOperator(t1);
			return nextTokenRaw();
		}
		if (fLastToken != null) {
			fLastToken.setNext(t1);
		}
		fLastToken = t1;
		return t1;
	}

	private void handlePragmaOperator(Token t1) throws OffsetLimitReachedException {
		Token t2 = fetchToken();
		int end;
		if (t2.getType() == IToken.tLPAREN) {
			Token t3 = fetchToken();
			end = t3.getEndOffset();
			final int tt = t3.getType();
			if (tt == IToken.tSTRING || tt == IToken.tLSTRING || tt == IToken.tUTF16STRING
					|| tt == IToken.tUTF32STRING) {
				Token t4 = fetchToken();
				end = t4.getEndOffset();
				if (t4.getType() == IToken.tRPAREN) {
					fLocationMap.encounterPragmaOperator(t1.getOffset(), t3.getOffset(), t3.getEndOffset(),
							t4.getEndOffset());
					return;
				} else {
					end = t3.getEndOffset();
					pushbackToken(t4);
				}
			} else {
				if (t3.getType() == IToken.tRPAREN) {
					// Consume closing parenthesis
					end = t3.getEndOffset();
				} else {
					end = t2.getEndOffset();
					pushbackToken(t3);
				}
			}
		} else {
			end = t1.getEndOffset();
			pushbackToken(t2);
		}
		fLocationMap.encounterProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, t1.getCharImage(), t1.getOffset(), end);
	}

	/**
	 * Returns next token for the parser. String literals are concatenated.
	 * @throws EndOfFileException when the end of the translation unit has been reached.
	 * @throws OffsetLimitReachedException see {@link Lexer}.
	 */
	@Override
	public IToken nextToken() throws EndOfFileException {
		if (isCancelled) {
			throw new ParseError(ParseError.ParseErrorKind.TIMEOUT_OR_CANCELLED);
		}

		Token t1 = fetchToken();
		char[] udlSuffix = null;

		final int tt1 = t1.getType();
		switch (tt1) {
		case IToken.tCOMPLETION:
			fHandledCompletion = true;
			break;

		case IToken.tEND_OF_INPUT:
			if (fContentAssistLimit < 0) {
				fPrefetchedTokens = t1;
				throw new EndOfFileException(t1.getOffset());
			}
			int useType = fHandledCompletion ? IToken.tEOC : IToken.tCOMPLETION;
			int sequenceNumber = fLocationMap.getSequenceNumberForOffset(fContentAssistLimit);
			t1 = new Token(useType, null, sequenceNumber, sequenceNumber);
			fHandledCompletion = true;
			break;

		case IToken.t_PRAGMA:
			handlePragmaOperator(t1);
			return nextToken();
		case IToken.tUSER_DEFINED_STRING_LITERAL:
			udlSuffix = getUserDefinedLiteralSuffix(t1);
			//$FALL-THROUGH$
		case IToken.tSTRING:
		case IToken.tLSTRING:
		case IToken.tUTF16STRING:
		case IToken.tUTF32STRING:
			StringType st = StringType.fromToken(t1);
			Token t2;
			StringBuilder buf = null;
			int endOffset = t1.getEndOffset();
			loop: while (true) {
				t2 = fetchToken();
				final int tt2 = t2.getType();
				switch (tt2) {
				case IToken.tUSER_DEFINED_STRING_LITERAL:
					if (udlSuffix == null) {
						udlSuffix = getUserDefinedLiteralSuffix(t2);
					} else if (!Arrays.equals(udlSuffix, getUserDefinedLiteralSuffix(t2))) {
						handleProblem(IProblem.PREPROCESSOR_MULTIPLE_USER_DEFINED_SUFFIXES_IN_CONCATENATION, udlSuffix,
								t2.getOffset(), endOffset);
					}
					//$FALL-THROUGH$
				case IToken.tLSTRING:
				case IToken.tSTRING:
				case IToken.tUTF16STRING:
				case IToken.tUTF32STRING:
					st = StringType.max(st, StringType.fromToken(t2));
					if (buf == null) {
						buf = new StringBuilder();
						appendStringContent(buf, t1);
					}
					appendStringContent(buf, t2);
					endOffset = t2.getEndOffset();
					continue loop;
				case IToken.tINACTIVE_CODE_START:
					// no support for inactive code after a string literal
					skipInactiveCode();
					continue loop;
				case IToken.t_PRAGMA:
					handlePragmaOperator(t2);
					continue loop;
				default:
					break loop;
				}
			}
			pushbackToken(t2);
			if (buf != null) {
				char[] prefix = st.getPrefix();
				final int imageLength = buf.length() + prefix.length + 2 + (udlSuffix == null ? 0 : udlSuffix.length);
				char[] image = new char[imageLength];
				int off = -1;
				int tokenType = st.getTokenValue();

				for (char c : prefix)
					image[++off] = c;

				image[++off] = '"';
				buf.getChars(0, buf.length(), image, ++off);
				off += buf.length();
				image[off] = '"';
				if (udlSuffix != null) {
					System.arraycopy(udlSuffix, 0, image, ++off, udlSuffix.length);
					tokenType = IToken.tUSER_DEFINED_STRING_LITERAL;
				}
				t1 = new TokenWithImage(tokenType, null, t1.getOffset(), endOffset, image);
			}
			break;

		case IToken.tSHIFTR:
			if (fSplitShiftRightOperator) {
				int offset = t1.getOffset();
				endOffset = t1.getEndOffset();

				t1.setType(IToken.tGT_in_SHIFTR);
				t1.setOffset(offset, offset + 1);
				t2 = new Token(IToken.tGT_in_SHIFTR, t1.fSource, offset + 1, endOffset);
				pushbackToken(t2);
			}
		}

		if (fLastToken != null) {
			fLastToken.setNext(t1);
		}
		fLastToken = t1;
		return t1;
	}

	@Override
	public void skipInactiveCode() throws OffsetLimitReachedException {
		final Lexer lexer = fCurrentContext.getLexer();
		if (lexer != null) {
			CodeState state = fCurrentContext.getCodeState();
			while (state != CodeState.eActive) {
				state = skipBranch(lexer, false);
			}
			fCurrentContext.clearInactiveCodeMarkerToken();
		}
	}

	@Override
	public int getCodeBranchNesting() {
		return fCurrentContext.getCodeBranchNesting();
	}

	private void appendStringContent(StringBuilder buf, Token t1) {
		final char[] image = t1.getCharImage();
		final int length = image.length;
		int start = 1;
		for (char c : image) {
			if (c == '"')
				break;
			start++;
		}

		if (length > 1) {
			int diff = 0;
			if (t1.getType() == IToken.tUSER_DEFINED_STRING_LITERAL) {
				diff = t1.getImage().lastIndexOf('"') - start;
			} else {
				diff = image[length - 1] == '"' ? length - start - 1 : length - start;
			}
			if (diff > 0) {
				buf.append(image, start, diff);
			}
		}
	}

	Token internalFetchToken(final ScannerContext uptoEndOfCtx, int options, boolean withinExpansion)
			throws OffsetLimitReachedException {

		Token ppToken = fCurrentContext.currentLexerToken();
		while (true) {
			switch (ppToken.getType()) {
			case Lexer.tBEFORE_INPUT:
				ppToken = fCurrentContext.nextPPToken();
				continue;

			case Lexer.tNEWLINE:
				if ((options & STOP_AT_NL) != 0) {
					return ppToken;
				}
				ppToken = fCurrentContext.nextPPToken();
				continue;

			case Lexer.tOTHER_CHARACTER:
				handleProblem(IProblem.SCANNER_BAD_CHARACTER, ppToken.getCharImage(), ppToken.getOffset(),
						ppToken.getEndOffset());
				ppToken = fCurrentContext.nextPPToken();
				continue;

			case IToken.tEND_OF_INPUT:
				if (fCurrentContext == uptoEndOfCtx || uptoEndOfCtx == null) {
					if (fCurrentContext == fRootContext && !fHandledEndOfTranslationUnit
							&& (options & STOP_AT_NL) == 0) {
						fHandledEndOfTranslationUnit = true;
						fLocationMap.endTranslationUnit(ppToken.getEndOffset(), fCurrentContext.getSignificantMacros());
					}
					return ppToken;
				}

				final ILocationCtx locationCtx = fCurrentContext.getLocationCtx();
				ASTInclusionStatement inc = locationCtx.getInclusionStatement();
				if (inc != null) {
					completeInclusion(inc);
				}
				fLocationMap.popContext(locationCtx);

				fCurrentContext.propagateSignificantMacros();
				fCurrentContext = fCurrentContext.getParent();
				assert fCurrentContext != null;

				ppToken = fCurrentContext.currentLexerToken();
				continue;

			case IToken.tPOUND: {
				final Lexer lexer = fCurrentContext.getLexer();
				if (lexer != null && lexer.currentTokenIsFirstOnLine()) {
					executeDirective(lexer, ppToken.getOffset(), withinExpansion);
					ppToken = fCurrentContext.currentLexerToken();
					continue;
				}
				break;
			}

			case IToken.tIDENTIFIER:
				fCurrentContext.nextPPToken(); // consume the identifier
				if ((options & NO_EXPANSION) == 0) {
					final Lexer lexer = fCurrentContext.getLexer();
					if (lexer != null && expandMacro(ppToken, lexer, options, withinExpansion)) {
						ppToken = fCurrentContext.currentLexerToken();
						continue;
					}

					final char[] name = ppToken.getCharImage();
					int tokenType = fKeywords.get(name);
					if (tokenType != fKeywords.undefined) {
						ppToken.setType(tokenType);
					}
				}
				return ppToken;

			case IToken.tINTEGER:
				if ((options & CHECK_NUMBERS) != 0) {
					checkNumber(ppToken, false);
				}
				break;

			case IToken.tFLOATINGPT:
				if ((options & CHECK_NUMBERS) != 0) {
					checkNumber(ppToken, true);
				}
				break;
			}
			fCurrentContext.nextPPToken();
			return ppToken;
		}
	}

	private void completeInclusion(ASTInclusionStatement inc) {
		final ISignificantMacros sig;
		CharArrayObjectMap<char[]> sigMacros = fCurrentContext.getSignificantMacros();
		if (sigMacros == null || sigMacros.isEmpty()) {
			sig = ISignificantMacros.NONE;
		} else {
			sig = new SignificantMacros(sigMacros);
		}
		inc.setSignificantMacros(sig);
		if (!inc.hasPragmaOnceSemantics()) {
			fFileContentProvider.addLoadedVersions(inc.getPath(), fCurrentContext.getLoadedVersionCount(), sig);
		}
	}

	private void checkNumber(Token number, final boolean isFloat) {
		final char[] image = number.getCharImage();
		boolean hasExponent = false;

		// Integer constants written in binary are a non-standard extension
		// supported by GCC since 4.3 and by some other C compilers
		// They consist of a prefix 0b or 0B, followed by a sequence of 0 and 1 digits
		// see http://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html
		boolean isBin = false;

		boolean isHex = false;
		boolean isOctal = false;
		boolean hasDot = false;

		boolean badSuffix = false;

		int pos = 0;
		if (image.length > 1) {
			if (image[0] == '0') {
				switch (image[++pos]) {
				case 'b':
				case 'B':
					isBin = true;
					++pos;
					break;
				case 'x':
				case 'X':
					isHex = true;
					++pos;
					break;
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
					if (!isFloat)
						isOctal = true;
					++pos;
					break;
				case '8':
				case '9':
					if (!isFloat) {
						handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(),
								number.getEndOffset());
						return;
					}
					++pos;
					break;
				}
			}
		}

		loop: for (; pos < image.length; pos++) {
			if (isBin) {
				switch (image[pos]) {
				case '0':
				case '1':
					continue;
				case '\'':
					if (fLexOptions.fSupportDigitSeparators) {
						continue;
					} else {
						break loop;
					}
				case 'e':
				case 'E':
				case '.':
					handleProblem(IProblem.SCANNER_FLOAT_WITH_BAD_PREFIX, "0b".toCharArray(), number.getOffset(), //$NON-NLS-1$
							number.getEndOffset());
					return;
				default:
					// 0 and 1 are the only allowed digits for binary integers
					// No floating point, exponents etc. are allowed
					break loop;
				}
			}
			switch (image[pos]) {
			// octal digits
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				continue;

			// decimal digits
			case '8':
			case '9':
				if (isOctal) {
					handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(), number.getEndOffset());
					return;
				}
				continue;

			// hex digits
			case 'a':
			case 'A':
			case 'b':
			case 'B':
			case 'c':
			case 'C':
			case 'd':
			case 'D':
			case 'f':
			case 'F':
				if (isHex && !hasExponent) {
					continue;
				}
				break loop;

			case '.':
				if (hasDot) {
					handleProblem(IProblem.SCANNER_BAD_FLOATING_POINT, image, number.getOffset(),
							number.getEndOffset());
					return;
				}
				hasDot = true;
				continue;

			// C++14 literal separator
			case '\'':
				if (fLexOptions.fSupportDigitSeparators) {
					continue;
				} else {
					break loop;
				}

				// check for exponent or hex digit
			case 'E':
			case 'e':
				if (isHex && !hasExponent) {
					continue;
				}
				if (isFloat && !isHex && !hasExponent && pos + 1 < image.length) {
					switch (image[pos + 1]) {
					case '+':
					case '-':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						hasExponent = true;
						++pos;
						continue;
					}
				}
				break loop;

			// check for hex float exponent
			case 'p':
			case 'P':
				if (isFloat && isHex && !hasExponent && pos + 1 < image.length) {
					switch (image[pos + 1]) {
					case '+':
					case '-':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						hasExponent = true;
						++pos;
						continue;
					}
				}
				break loop;

			default:
				break loop;
			}
		}
		if (pos < image.length) {
			char c = image[pos];
			if (Character.isLetter(c) || c == '_') {
				// check the suffix
				final int suffixStart = pos;
				loop: for (; pos < image.length; pos++) {
					c = image[pos];
					switch (c) {
					case 'u':
					case 'U':
					case 'L':
					case 'l':
						continue;
					case 'f':
					case 'F':
						if (isFloat) {
							continue loop;
						}

						//$FALL-THROUGH$
					case 'a':
					case 'b':
					case 'c':
					case 'd':
					case 'e':
					case 'g':
					case 'h':
					case 'i':
					case 'j':
					case 'k':
					case 'm':
					case 'n':
					case 'o':
					case 'p':
					case 'q':
					case 'r':
					case 's':
					case 't':
					case 'v':
					case 'w':
					case 'x':
					case 'y':
					case 'z':
					case 'A':
					case 'B':
					case 'C':
					case 'D':
					case 'E':
					case 'G':
					case 'H':
					case 'I':
					case 'J':
					case 'K':
					case 'M':
					case 'N':
					case 'O':
					case 'P':
					case 'Q':
					case 'R':
					case 'S':
					case 'T':
					case 'V':
					case 'W':
					case 'X':
					case 'Y':
					case 'Z':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
					case '_':
						if (fLexOptions.fSupportUserDefinedLiterals) {
							continue loop;
						}
					}
					for (int i = 0; i < fAdditionalNumericLiteralSuffixes.length; i++) {
						if (fAdditionalNumericLiteralSuffixes[i] == c) {
							continue loop;
						} else {
							badSuffix = true;
						}
					}

					if (badSuffix) {
						char[] suffix = CharArrayUtils.subarray(image, suffixStart, -1);
						handleProblem(IProblem.SCANNER_CONSTANT_WITH_BAD_SUFFIX, suffix, number.getOffset(),
								number.getEndOffset());
						return;
					}
				}
				return;
			}
		} else {
			return;
		}

		if (isBin) {
			// The check for bin has to come before float, otherwise binary integers
			// with float components get flagged as BAD_FLOATING_POINT
			handleProblem(IProblem.SCANNER_BAD_BINARY_FORMAT, image, number.getOffset(), number.getEndOffset());
		} else if (isFloat) {
			handleProblem(IProblem.SCANNER_BAD_FLOATING_POINT, image, number.getOffset(), number.getEndOffset());
		} else if (isHex) {
			handleProblem(IProblem.SCANNER_BAD_HEX_FORMAT, image, number.getOffset(), number.getEndOffset());
		} else if (isOctal) {
			handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(), number.getEndOffset());
		} else {
			handleProblem(IProblem.SCANNER_BAD_DECIMAL_FORMAT, image, number.getOffset(), number.getEndOffset());
		}
		return;

	}

	private <T> T findInclusion(final String includeDirective, final boolean quoteInclude, final boolean includeNext,
			final String currentFile, final IIncludeFileTester<T> tester) {
		T reader = null;
		String absoluteInclusionPath = getAbsoluteInclusionPath(includeDirective, currentFile);
		if (absoluteInclusionPath != null) {
			return tester.checkFile(absoluteInclusionPath, false, null);
		}

		if (currentFile != null && quoteInclude && !includeNext
				&& !fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory()) {
			// Check to see if we find a match in the directory of the current file
			final File currentDir = new File(currentFile).getParentFile();
			if (currentDir != null) {
				final String fileLocation = ScannerUtility.createReconciledPath(currentDir.getAbsolutePath(),
						includeDirective);
				reader = tester.checkFile(fileLocation, false, null);
				if (reader != null) {
					return reader;
				}
			}
		}

		// Now we need to search for the file on the include search path.
		// If this is a include_next directive then the search starts with the directory
		// in the search path after the one where the current file was found.
		IncludeSearchPathElement searchAfter = null;
		if (includeNext && currentFile != null) {
			searchAfter = fCurrentContext.getFoundOnPath();
			if (searchAfter == null) {
				// the current file was found without search path
				String directive = fCurrentContext.getFoundViaDirective();
				if (directive == null) {
					directive = new File(currentFile).getName();
				}
				searchAfter = findFileInIncludePath(currentFile, directive);
			}
		}

		for (IncludeSearchPathElement path : fIncludeSearchPath.getElements()) {
			if (searchAfter != null) {
				if (searchAfter.equals(path)) {
					searchAfter = null;
				}
			} else if (quoteInclude || !path.isForQuoteIncludesOnly()) {
				String fileLocation = path.getLocation(includeDirective);
				if (fileLocation != null) {
					reader = tester.checkFile(fileLocation, false, path);
					if (reader != null) {
						return reader;
					}
				}
			}
		}
		if (fIncludeFileResolutionHeuristics != null) {
			String location = fIncludeFileResolutionHeuristics.findInclusion(includeDirective, currentFile);
			if (location != null) {
				return tester.checkFile(location, true, null);
			}
		}
		return null;
	}

	public static String getAbsoluteInclusionPath(String includeDirective, String currentFile) {
		// Filename is an absolute path.
		if (new File(includeDirective).isAbsolute()) {
			return includeDirective;
		}
		// Filename is a Linux absolute path on a Windows machine.
		if (File.separatorChar == '\\' && !includeDirective.isEmpty()) {
			final char firstChar = includeDirective.charAt(0);
			if (firstChar == '\\' || firstChar == '/') {
				if (currentFile != null && currentFile.length() > 1 && currentFile.charAt(1) == ':') {
					return currentFile.substring(0, 2) + includeDirective;
				}
				return includeDirective;
			}
		}
		return null;
	}

	private IncludeSearchPathElement findFileInIncludePath(String file, String includeDirective) {
		for (IncludeSearchPathElement path : fIncludeSearchPath.getElements()) {
			String fileLocation = path.getLocation(includeDirective);
			if (file.equals(fileLocation)) {
				return path;
			}
		}
		return null;
	}

	@Override
	public String toString() {
		StringBuilder buffer = new StringBuilder("Scanner @ file:"); //$NON-NLS-1$
		buffer.append(fCurrentContext.toString());
		buffer.append(" line: "); //$NON-NLS-1$
		buffer.append(fLocationMap.getCurrentLineNumber(fCurrentContext.currentLexerToken().getOffset()));
		return buffer.toString();
	}

	private void addMacroDefinition(IIndexMacro macro) {
		try {
			final char[] expansionImage = macro.getExpansionImage();
			if (expansionImage == null) {
				// this is an undef
				fMacroDictionary.remove(macro.getNameCharArray());
			} else {
				PreprocessorMacro result = MacroDefinitionParser.parseMacroDefinition(macro.getNameCharArray(),
						macro.getParameterList(), expansionImage);
				fLocationMap.registerMacroFromIndex(result, macro.getDefinition(), -1);
				fMacroDictionary.put(result.getNameCharArray(), result);
			}
		} catch (Exception e) {
			fLog.traceLog("Invalid macro definition: '" + macro.getName() + "'"); //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	public ILocationResolver getLocationMap() {
		return fLocationMap;
	}

	@Override
	public void handleComment(boolean isBlockComment, int offset, int endOffset, AbstractCharArray input) {
		fLocationMap.encounteredComment(offset, endOffset, isBlockComment, input);
	}

	@Override
	public void handleProblem(int id, char[] arg, int offset, int endOffset) {
		fLocationMap.encounterProblem(id, arg, offset, endOffset);
	}

	/**
	 * Assumes that the pound token has not yet been consumed
	 * @since 5.0
	 */
	private void executeDirective(final Lexer lexer, final int startOffset, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final Token ident = lexer.nextToken();
		switch (ident.getType()) {
		case IToken.tCOMPLETION:
			lexer.nextToken();
			Token completionToken = new TokenWithImage(ident.getType(), null, startOffset, ident.getEndOffset(),
					("#" + ident.getImage()).toCharArray()); //$NON-NLS-1$
			throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, completionToken);

		case Lexer.tNEWLINE:
			return;

		case IToken.tEND_OF_INPUT:
		case IToken.tINTEGER:
			lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			return;

		case IToken.tIDENTIFIER:
			break;

		default:
			int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, ident.getCharImage(), startOffset, endOffset);
			return;
		}

		// we have an identifier
		final char[] name = ident.getCharImage();
		final int type = fPPKeywords.get(name);
		int condEndOffset;
		switch (type) {
		case IPreprocessorDirective.ppImport:
		case IPreprocessorDirective.ppInclude:
		case IPreprocessorDirective.ppInclude_next:
			executeInclude(lexer, startOffset, type, fCurrentContext.getCodeState() == CodeState.eActive,
					withinExpansion);
			break;
		case IPreprocessorDirective.ppDefine:
			CodeState state = fCurrentContext.getCodeState();
			if (state == CodeState.eSkipInactive) {
				lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			} else {
				executeDefine(lexer, startOffset, state == CodeState.eActive);
			}
			break;
		case IPreprocessorDirective.ppUndef:
			state = fCurrentContext.getCodeState();
			if (state == CodeState.eSkipInactive) {
				lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			} else {
				executeUndefine(lexer, startOffset, fCurrentContext.getCodeState() == CodeState.eActive);
			}
			break;
		case IPreprocessorDirective.ppIfdef:
			if (executeIfdef(lexer, startOffset, false, withinExpansion) == CodeState.eSkipInactive)
				skipOverConditionalCode(lexer, withinExpansion);
			break;
		case IPreprocessorDirective.ppIfndef:
			if (executeIfdef(lexer, startOffset, true, withinExpansion) == CodeState.eSkipInactive)
				skipOverConditionalCode(lexer, withinExpansion);
			break;
		case IPreprocessorDirective.ppIf:
			if (executeIf(lexer, startOffset, false, withinExpansion) == CodeState.eSkipInactive)
				skipOverConditionalCode(lexer, withinExpansion);
			break;
		case IPreprocessorDirective.ppElif:
			if (executeIf(lexer, startOffset, true, withinExpansion) == CodeState.eSkipInactive) {
				skipOverConditionalCode(lexer, withinExpansion);
			}
			break;
		case IPreprocessorDirective.ppElse:
			if (executeElse(lexer, startOffset, withinExpansion) == CodeState.eSkipInactive) {
				skipOverConditionalCode(lexer, withinExpansion);
			}
			break;
		case IPreprocessorDirective.ppEndif:
			executeEndif(lexer, startOffset, withinExpansion);
			break;
		case IPreprocessorDirective.ppWarning:
		case IPreprocessorDirective.ppError:
			int condOffset = lexer.nextToken().getOffset();
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			// Missing argument
			if (condEndOffset < condOffset) {
				condOffset = condEndOffset;
			}
			if (fCurrentContext.getCodeState() == CodeState.eActive) {
				int endOffset = lexer.currentToken().getEndOffset();
				final char[] warning = lexer.getInputChars(condOffset, condEndOffset);
				final int id = type == IPreprocessorDirective.ppError ? IProblem.PREPROCESSOR_POUND_ERROR
						: IProblem.PREPROCESSOR_POUND_WARNING;
				handleProblem(id, warning, condOffset, condEndOffset);
				fLocationMap.encounterPoundError(startOffset, condOffset, condEndOffset, endOffset);
			}
			break;
		case IPreprocessorDirective.ppPragma:
			Token pragmaToken = lexer.nextToken();
			condOffset = pragmaToken.getOffset();
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			// Missing argument
			if (condEndOffset < condOffset) {
				condOffset = condEndOffset;
			}
			if (fCurrentContext.getCodeState() == CodeState.eActive) {
				int endOffset = lexer.currentToken().getEndOffset();
				fLocationMap.encounterPoundPragma(startOffset, condOffset, condEndOffset, endOffset);
				if (CharArrayUtils.equals(ONCE, pragmaToken.getCharImage())) {
					IFileNomination nom = fLocationMap.reportPragmaOnceSemantics(fCurrentContext.getLocationCtx());
					fFileContentProvider.reportPragmaOnceSemantics(getCurrentFilename(), nom);
				}
			}
			break;
		case IPreprocessorDirective.ppIgnore:
			lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			break;
		default:
			int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			if (fCurrentContext.getCodeState() == CodeState.eActive) {
				handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, ident.getCharImage(), startOffset, endOffset);
			}
			break;
		}
	}

	private void executeInclude(final Lexer lexer, int poundOffset, int includeType, boolean active,
			boolean withinExpansion) throws OffsetLimitReachedException {
		// Make sure to clear the extern include guard.
		final char[] externGuard = fExternIncludeGuard;
		fExternIncludeGuard = null;

		if (withinExpansion) {
			final char[] name = lexer.currentToken().getCharImage();
			final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, name, poundOffset, endOffset);
			return;
		}

		lexer.setInsideIncludeDirective(true);
		final Token header = lexer.nextToken();
		lexer.setInsideIncludeDirective(false);

		int condEndOffset = header.getEndOffset();
		final int[] nameOffsets = new int[] { header.getOffset(), condEndOffset };
		char[] headerName = null;
		boolean userInclude = true;

		switch (header.getType()) {
		case Lexer.tSYSTEM_HEADER_NAME:
			userInclude = false;
			headerName = extractHeaderName(header.getCharImage(), '<', '>', nameOffsets);
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			break;

		case Lexer.tQUOTE_HEADER_NAME:
			headerName = extractHeaderName(header.getCharImage(), '"', '"', nameOffsets);
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			break;

		case IToken.tCOMPLETION:
			throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, header);

		case IToken.tIDENTIFIER:
			TokenList tl = new TokenList();
			condEndOffset = nameOffsets[1] = getTokensWithinPPDirective(false, tl, false);
			Token t = tl.first();
			if (t != null) {
				switch (t.getType()) {
				case IToken.tSTRING:
					headerName = extractHeaderName(t.getCharImage(), '"', '"', new int[] { 0, 0 });
					break;
				case IToken.tLT:
					userInclude = false;
					boolean complete = false;
					StringBuilder buf = new StringBuilder();
					t = (Token) t.getNext();
					while (t != null) {
						if (t.getType() == IToken.tGT) {
							complete = true;
							break;
						}
						buf.append(t.getImage());
						t = (Token) t.getNext();
					}
					if (complete) {
						headerName = new char[buf.length()];
						buf.getChars(0, buf.length(), headerName, 0);
					}
				}
			}
			break;

		default:
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			break;
		}

		if (headerName == null || headerName.length == 0) {
			if (active) {
				handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, lexer.getInputChars(poundOffset, condEndOffset),
						poundOffset, condEndOffset);
			}
			return;
		}

		if (includeType == IPreprocessorDirective.ppImport) {
			// Imports are executed only once.
			// See http://gcc.gnu.org/onlinedocs/gcc-3.2/cpp/Obsolete-once-only-headers.html.
			if (fImports != null && fImports.containsKey(headerName))
				return;
			if (active) {
				if (fImports == null)
					fImports = new CharArraySet(0);
				fImports.put(headerName);
			}
		}

		if ((active && fCurrentContext.getDepth() == MAX_INCLUSION_DEPTH)
				|| (fPreventInclusion != null && fPreventInclusion.containsKey(headerName))) {
			handleProblem(IProblem.PREPROCESSOR_EXCEEDS_MAXIMUM_INCLUSION_DEPTH,
					lexer.getInputChars(poundOffset, condEndOffset), poundOffset, condEndOffset);
			if (fPreventInclusion == null)
				fPreventInclusion = new CharArraySet(0);
			fPreventInclusion.put(headerName);
			return;
		}

		boolean includeNext = includeType == IPreprocessorDirective.ppInclude_next;
		final String includeDirective = new String(headerName);
		if (!active) {
			// Inactive include
			String path = null;
			boolean isHeuristic = false;
			IFileNomination nominationDelegate = null;

			if (externGuard != null) {
				// #ifndef GUARD
				//    #include "file.h"
				// #endif
				// When the extern guard matches we need to resolve the inclusion. We don't actually
				// check whether the guard matches.
				final IncludeResolution resolved = findInclusion(includeDirective, userInclude, includeNext,
						getCurrentFilename(), createPathTester);
				if (resolved != null) {
					nominationDelegate = fFileContentProvider.isIncludedWithPragmaOnceSemantics(resolved.fLocation);
					if (nominationDelegate != null) {
						path = resolved.fLocation;
						isHeuristic = resolved.fHeuristic;
					}
				}
			}
			fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset, headerName,
					path, userInclude, active, isHeuristic, nominationDelegate);
			return;
		}

		// Active include
		final InternalFileContent fi = findInclusion(includeDirective, userInclude, includeNext, getCurrentFilename(),
				createCodeReaderTester);
		if (fi == null) {
			// Unresolved active include
			final int len = headerName.length + 2;
			StringBuilder name = new StringBuilder(len);
			name.append(userInclude ? '"' : '<');
			name.append(headerName);
			name.append(userInclude ? '"' : '>');

			final char[] nameChars = new char[len];
			name.getChars(0, len, nameChars, 0);
			handleProblem(IProblem.PREPROCESSOR_INCLUSION_NOT_FOUND, nameChars, poundOffset, condEndOffset);
			fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset, headerName,
					null, userInclude, active, false, null);
			return;
		}

		// Resolved active include
		final String path = fi.getFileLocation();
		final boolean isHeuristic = fi.isFoundByHeuristics();
		final boolean pragmaOnceContext = fCurrentContext.isPragmaOnce();

		IFileNomination nominationDelegate = null;
		ASTInclusionStatement stmt = null;
		List<ISignificantMacros> loadedVerisons = null;
		switch (fi.getKind()) {
		case FOUND_IN_INDEX:
			// Pulled in from index
			nominationDelegate = fi.getFilesIncluded().get(0);
			try {
				ISignificantMacros sm = nominationDelegate.getSignificantMacros();
				fCurrentContext.addSignificantMacros(sm);
				if (pragmaOnceContext && !nominationDelegate.hasPragmaOnceSemantics())
					loadedVerisons = fFileContentProvider.getLoadedVersions(path);
			} catch (CoreException e) {
			}

			processInclusionFromIndex(poundOffset, fi, true);
			break;
		case USE_SOURCE:
			// Will be parsed
			AbstractCharArray source = fi.getSource();
			if (source != null) {
				ILocationCtx ctx = fLocationMap.pushInclusion(poundOffset, nameOffsets[0], nameOffsets[1],
						condEndOffset, source, path, headerName, userInclude, isHeuristic, fi.isSource());
				ScannerContext fctx = new ScannerContext(ctx, fCurrentContext,
						new Lexer(source, fLexOptions, this, this));
				fctx.setFoundOnPath(fi.getFoundOnPath(), includeDirective);
				detectIncludeGuard(path, source, fctx);
				fCurrentContext = fctx;
				stmt = ctx.getInclusionStatement();
				stmt.setIncludedFileTimestamp(fi.getTimestamp());
				stmt.setIncludedFileSize(fi.getFileSize());
				stmt.setIncludedFileContentsHash(source.getContentsHash());
				stmt.setIncludedFileReadTime(fi.getReadTime());
				stmt.setErrorInIncludedFile(source.hasError());
				if (!fCurrentContext.isPragmaOnce()) {
					// Track the loaded version count, even in a non-pragma-once context.
					loadedVerisons = fFileContentProvider.getLoadedVersions(path);
					fctx.setLoadedVersionCount(loadedVerisons.size());
				}
			}
			fLocationMap.parsingFile(fFileContentProvider, fi);
			break;

		case SKIP_FILE:
			// Already included or fast parsing mode.
			break;
		}
		if (stmt == null) {
			// Found in index or skipped.
			stmt = fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset,
					headerName, path, userInclude, active, isHeuristic, nominationDelegate);
		}
		// In a pragma once context store loaded versions of this non-pragma-once include
		if (pragmaOnceContext && loadedVerisons != null && !loadedVerisons.isEmpty()) {
			stmt.setLoadedVersions(loadedVerisons.toArray(new ISignificantMacros[loadedVerisons.size()]));
		}
	}

	private void processInclusionFromIndex(int offset, InternalFileContent fi, boolean updateContext) {
		List<IIndexMacro> mdefs = fi.getMacroDefinitions();
		for (IIndexMacro macro : mdefs) {
			addMacroDefinition(macro);
			if (updateContext)
				fCurrentContext.internalModification(macro.getNameCharArray());
		}
		for (FileVersion version : fi.getNonPragmaOnceVersions()) {
			fFileContentProvider.addLoadedVersions(version.fPath, Integer.MAX_VALUE, version.fSigMacros);
		}
		fLocationMap.skippedFile(fLocationMap.getSequenceNumberForOffset(offset), fi);
	}

	private char[] extractHeaderName(final char[] image, final char startDelim, final char endDelim, int[] offsets) {
		char[] headerName;
		int start = 0;
		int length = image.length;
		if (length > 0 && image[length - 1] == endDelim) {
			length--;
			offsets[1]--;
			if (length > 0 && image[0] == startDelim) {
				offsets[0]++;
				start++;
				length--;
			}
		}
		headerName = new char[length];
		System.arraycopy(image, start, headerName, 0, length);
		return headerName;
	}

	private void executeDefine(final Lexer lexer, int startOffset, boolean isActive)
			throws OffsetLimitReachedException {
		try {
			ObjectStyleMacro macrodef = fMacroDefinitionParser.parseMacroDefinition(lexer, this);
			if (isActive) {
				final char[] macroName = macrodef.getNameCharArray();
				fMacroDictionary.put(macroName, macrodef);
				fCurrentContext.internalModification(macroName);
			}

			final Token name = fMacroDefinitionParser.getNameToken();
			fLocationMap.encounterPoundDefine(startOffset, name.getOffset(), name.getEndOffset(),
					macrodef.getExpansionOffset(), macrodef.getExpansionEndOffset(), isActive, macrodef);
		} catch (InvalidMacroDefinitionException e) {
			lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			handleProblem(IProblem.PREPROCESSOR_INVALID_MACRO_DEFN, e.fName, e.fStartOffset, e.fEndOffset);
		}
	}

	private void executeUndefine(Lexer lexer, int startOffset, boolean isActive) throws OffsetLimitReachedException {
		final Token name = lexer.nextToken();
		final int tt = name.getType();
		if (tt != IToken.tIDENTIFIER) {
			if (tt == IToken.tCOMPLETION) {
				throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, name);
			}
			lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			handleProblem(IProblem.PREPROCESSOR_INVALID_MACRO_DEFN, name.getCharImage(), startOffset,
					name.getEndOffset());
			return;
		}
		lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
		final int endOffset = lexer.currentToken().getEndOffset();
		final char[] namechars = name.getCharImage();
		PreprocessorMacro definition;
		if (isActive) {
			definition = fMacroDictionary.remove(namechars, 0, namechars.length);
			fCurrentContext.internalModification(namechars);
		} else {
			definition = fMacroDictionary.get(namechars);
		}
		fLocationMap.encounterPoundUndef(definition, startOffset, name.getOffset(), name.getEndOffset(), endOffset,
				namechars, isActive);
	}

	private CodeState executeIfdef(Lexer lexer, int offset, boolean isIfndef, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final Token name = lexer.nextToken();
		lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
		final int tt = name.getType();
		final int nameOffset = name.getOffset();
		final int nameEndOffset = name.getEndOffset();
		final int endOffset = lexer.currentToken().getEndOffset();

		boolean isTaken = false;
		PreprocessorMacro macro = null;
		final Conditional conditional = fCurrentContext.newBranch(BranchKind.eIf, withinExpansion);
		if (conditional.canHaveActiveBranch(withinExpansion)) {
			// we need an identifier
			if (tt != IToken.tIDENTIFIER) {
				if (tt == IToken.tCOMPLETION) {
					throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, name);
				}
				// report problem and treat as inactive
				handleProblem(IProblem.PREPROCESSOR_DEFINITION_NOT_FOUND, name.getCharImage(), offset, nameEndOffset);
			} else {
				final char[] namechars = name.getCharImage();
				if (isIfndef) {
					if (IncludeGuardDetection.detectIncludeEndif(lexer)) {
						fExternIncludeGuard = namechars;
					}
				}
				macro = fMacroDictionary.get(namechars);
				isTaken = (macro == null) == isIfndef;
				if (macro == null) {
					macro = new UndefinedMacro(namechars);
					fCurrentContext.significantMacroUndefined(namechars);
				} else {
					fCurrentContext.significantMacroDefined(namechars);
				}
			}
		}

		ASTPreprocessorNode stmt;
		if (isIfndef) {
			stmt = fLocationMap.encounterPoundIfndef(offset, nameOffset, nameEndOffset, endOffset, isTaken, macro);
		} else {
			stmt = fLocationMap.encounterPoundIfdef(offset, nameOffset, nameEndOffset, endOffset, isTaken, macro);
		}
		if (!conditional.isActive(withinExpansion))
			stmt.setInactive();

		return fCurrentContext.setBranchState(conditional, isTaken, withinExpansion, offset);
	}

	private CodeState executeIf(Lexer lexer, int startOffset, boolean isElif, boolean withinExpansion)
			throws OffsetLimitReachedException {
		Conditional cond = fCurrentContext.newBranch(isElif ? BranchKind.eElif : BranchKind.eIf, withinExpansion);
		if (cond == null) {
			char[] name = lexer.currentToken().getCharImage();
			int condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, name, startOffset, condEndOffset);
			return fCurrentContext.getCodeState();
		}

		boolean isTaken = false;
		IASTName[] refs = IASTName.EMPTY_NAME_ARRAY;
		int condOffset = lexer.nextToken().getOffset();
		int condEndOffset, endOffset;

		if (cond.canHaveActiveBranch(withinExpansion)) {
			char[] macro = IncludeGuardDetection.detectIfNotDefinedIncludeEndif(lexer);
			if (macro != null) {
				fExternIncludeGuard = macro;
			}

			TokenList condition = new TokenList();
			condEndOffset = getTokensWithinPPDirective(true, condition, withinExpansion);
			endOffset = lexer.currentToken().getEndOffset();

			if (condition.first() == null) {
				handleProblem(IProblem.SCANNER_EXPRESSION_SYNTAX_ERROR, null, startOffset, endOffset);
			} else {
				try {
					fExpressionEvaluator.clearMacrosInDefinedExpression();
					isTaken = fExpressionEvaluator.evaluate(condition, fMacroDictionary, fLocationMap);
					refs = fExpressionEvaluator.clearMacrosInDefinedExpression();
					for (IASTName iastName : refs) {
						IBinding mb = iastName.getBinding();
						if (mb instanceof UndefinedMacro) {
							fCurrentContext.significantMacroUndefined(iastName.toCharArray());
						} else {
							fCurrentContext.significantMacroDefined(iastName.toCharArray());
						}
					}
				} catch (EvalException e) {
					handleProblem(e.getProblemID(), e.getProblemArg(), condOffset, endOffset);
				}
			}
		} else {
			condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
			endOffset = lexer.currentToken().getEndOffset();
		}

		ASTPreprocessorNode stmt;
		if (isElif) {
			stmt = fLocationMap.encounterPoundElif(startOffset, condOffset, condEndOffset, endOffset, isTaken, refs);
		} else {
			stmt = fLocationMap.encounterPoundIf(startOffset, condOffset, condEndOffset, endOffset, isTaken, refs);
		}
		if (!cond.isActive(withinExpansion))
			stmt.setInactive();

		return fCurrentContext.setBranchState(cond, isTaken, withinExpansion, startOffset);
	}

	private CodeState executeElse(final Lexer lexer, final int startOffset, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
		Conditional cond = fCurrentContext.newBranch(BranchKind.eElse, withinExpansion);
		if (cond == null) {
			handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, Keywords.cELSE, startOffset, endOffset);
			return fCurrentContext.getCodeState();
		}

		final boolean isTaken = cond.canHaveActiveBranch(withinExpansion);
		ASTElse stmt = fLocationMap.encounterPoundElse(startOffset, endOffset, isTaken);
		if (!cond.isActive(withinExpansion))
			stmt.setInactive();
		return fCurrentContext.setBranchState(cond, isTaken, withinExpansion, startOffset);
	}

	private CodeState executeEndif(Lexer lexer, int startOffset, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
		final Conditional cond = fCurrentContext.newBranch(BranchKind.eEnd, withinExpansion);
		if (cond == null) {
			handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, Keywords.cENDIF, startOffset, endOffset);
		} else {
			ASTEndif stmt = fLocationMap.encounterPoundEndIf(startOffset, endOffset);
			if (!cond.isActive(withinExpansion))
				stmt.setInactive();
		}
		return fCurrentContext.setBranchEndState(cond, withinExpansion, startOffset);
	}

	/**
	 * Runs the preprocessor on the rest of the line, storing the tokens in the holder supplied.
	 * Macro expansion is reported to the location map.
	 * In case isCondition is set to <code>true</code>, identifiers with image 'defined' are
	 * converted to the defined-token and its argument is not macro expanded.
	 * Returns the end-offset of the last token that was consumed.
	 */
	private int getTokensWithinPPDirective(boolean isCondition, TokenList result, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final ScannerContext scannerCtx = fCurrentContext;
		scannerCtx.clearInactiveCodeMarkerToken();
		int options = STOP_AT_NL | REPORT_SIGNIFICANT_MACROS;
		if (isCondition)
			options |= PROTECT_INTRINSICS;

		loop: while (true) {
			Token t = internalFetchToken(scannerCtx, options, withinExpansion);
			switch (t.getType()) {
			case IToken.tEND_OF_INPUT:
			case IToken.tCOMPLETION:
				scannerCtx.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); // make sure the exception is thrown.
				break loop;
			case Lexer.tNEWLINE:
				break loop;
			case IToken.tIDENTIFIER:
				break;
			case tDEFINED:
				options |= NO_EXPANSION;
				break;
			case t__HAS_FEATURE:
				options |= NO_EXPANSION;
				break;
			case IToken.tLPAREN:
				break;
			default:
				options &= ~NO_EXPANSION;
				break;
			}
			result.append(t);
		}
		// make sure an exception is thrown if we are running content assist at the end of the line
		return scannerCtx.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
	}

	private void skipOverConditionalCode(final Lexer lexer, boolean withinExpansion)
			throws OffsetLimitReachedException {
		CodeState state = CodeState.eSkipInactive;
		while (state == CodeState.eSkipInactive) {
			state = skipBranch(lexer, withinExpansion);
		}
	}

	private CodeState skipBranch(final Lexer lexer, boolean withinExpansion) throws OffsetLimitReachedException {
		while (true) {
			final Token pound = lexer.nextDirective();
			int tt = pound.getType();
			if (tt != IToken.tPOUND) {
				if (tt == IToken.tCOMPLETION) {
					// completion in inactive code
					throw new OffsetLimitReachedException(ORIGIN_INACTIVE_CODE, pound);
				}
				// must be the end of the lexer
				return CodeState.eActive;
			}
			final Token ident = lexer.nextToken();
			tt = ident.getType();
			if (tt != IToken.tIDENTIFIER) {
				if (tt == IToken.tCOMPLETION) {
					// completion in inactive directive
					throw new OffsetLimitReachedException(ORIGIN_INACTIVE_CODE, ident);
				}
				lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
				continue;
			}

			// we have an identifier
			final char[] name = ident.getCharImage();
			final int type = fPPKeywords.get(name);
			switch (type) {
			case IPreprocessorDirective.ppImport:
			case IPreprocessorDirective.ppInclude:
			case IPreprocessorDirective.ppInclude_next:
				executeInclude(lexer, pound.getOffset(), type, false, withinExpansion);
				break;
			case IPreprocessorDirective.ppIfdef:
				return executeIfdef(lexer, pound.getOffset(), false, withinExpansion);
			case IPreprocessorDirective.ppIfndef:
				return executeIfdef(lexer, pound.getOffset(), true, withinExpansion);
			case IPreprocessorDirective.ppIf:
				return executeIf(lexer, pound.getOffset(), false, withinExpansion);
			case IPreprocessorDirective.ppElif:
				return executeIf(lexer, pound.getOffset(), true, withinExpansion);
			case IPreprocessorDirective.ppElse:
				return executeElse(lexer, pound.getOffset(), withinExpansion);
			case IPreprocessorDirective.ppEndif:
				return executeEndif(lexer, pound.getOffset(), withinExpansion);
			default:
				lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE);
				break;
			}
		}
	}

	/**
	 * The method assumes that the identifier is consumed.
	 * <p>
	 * Checks whether the identifier causes a macro expansion. May advance the current lexer
	 * over newlines to check for the opening bracket succeeding the identifier.
	 * <p>
	 * If applicable the macro is expanded and the resulting tokens are put onto a new context.
	 * @param identifier the token where macro expansion may occur.
	 * @param lexer the input for the expansion.
	 * @param stopAtNewline whether or not tokens to be read are limited to the current line.
	 * @param isPPCondition whether the expansion is inside of a preprocessor condition. This
	 * implies a specific handling for the defined token.
	 */
	private boolean expandMacro(final Token identifier, Lexer lexer, int options, boolean withinExpansion)
			throws OffsetLimitReachedException {
		final boolean reportSignificant = (options & REPORT_SIGNIFICANT_MACROS) != 0;
		final char[] name = identifier.getCharImage();
		if ((options & PROTECT_INTRINSICS) != 0) {
			if (CharArrayUtils.equals(name, Keywords.cDEFINED)) {
				identifier.setType(tDEFINED);
				return false;
			}
			if (CharArrayUtils.equals(name, Keywords.c__HAS_FEATURE)) {
				identifier.setType(t__HAS_FEATURE);
				return false;
			}
		}
		PreprocessorMacro macro = fMacroDictionary.get(name);
		if (macro == null) {
			if (reportSignificant && (options & IGNORE_UNDEFINED_SIGNIFICANT_MACROS) == 0)
				fCurrentContext.significantMacroUndefined(name);
			return false;
		}
		boolean stopAtNewline = (options & STOP_AT_NL) != 0;
		if (macro instanceof FunctionStyleMacro) {
			Token t = lexer.currentToken();
			if (!stopAtNewline) {
				while (t.getType() == Lexer.tNEWLINE) {
					t = lexer.nextToken();
				}
			}
			if (t.getType() != IToken.tLPAREN) {
				if (reportSignificant)
					fCurrentContext.significantMacro(macro);
				return false;
			}
		}
		final boolean contentAssist = fContentAssistLimit >= 0 && fCurrentContext == fRootContext;
		final ITokenSequence input = stopAtNewline ? fLineInputToMacroExpansion : fInputToMacroExpansion;
		final MacroExpander expander = withinExpansion
				? new MacroExpander(this, fMacroDictionary, fLocationMap, fLexOptions)
				: fMacroExpander;
		TokenList replacement = expander.expand(input, options, macro, identifier, contentAssist, fCurrentContext);
		final IASTName[] expansions = expander.clearImplicitExpansions();
		final ImageLocationInfo[] ili = expander.clearImageLocationInfos();
		final Token last = replacement.last();
		final int length = last == null ? 0 : last.getEndOffset();
		ILocationCtx ctx = fLocationMap.pushMacroExpansion(identifier.getOffset(), identifier.getEndOffset(),
				lexer.getLastEndOffset(), length, macro, expansions, ili);
		fCurrentContext = new ScannerContext(ctx, fCurrentContext, replacement);
		return true;
	}

	/**
	 * Returns the user-defined suffix of a user-define string literal.
	 *
	 * @param token
	 * @return the suffix of the token, if it exists
	 */
	private static char[] getUserDefinedLiteralSuffix(IToken token) {
		if (token.getType() != IToken.tUSER_DEFINED_STRING_LITERAL)
			throw new IllegalArgumentException();

		char[] image = token.getCharImage();
		int offset = CharArrayUtils.lastIndexOf('"', image) + 1;
		if (offset <= 0)
			throw new IllegalArgumentException();
		if (offset == image.length)
			return CharArrayUtils.EMPTY_CHAR_ARRAY;
		return CharArrayUtils.subarray(image, offset, image.length);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T getAdapter(Class<T> adapter) {
		if (adapter.isAssignableFrom(fMacroExpander.getClass())) {
			return (T) fMacroExpander;
		}
		return null;
	}

	/**
	 * Return whether 'name' is a macro whose definition is provided by the
	 * preprocessor, like __LINE__, __FILE__, __DATE__ or __TIME__.
	 */
	public static boolean isPreprocessorProvidedMacro(char[] name) {
		return CharArrayUtils.equals(__LINE__.getNameCharArray(), name)
				|| CharArrayUtils.equals(__FILE__.getNameCharArray(), name)
				|| CharArrayUtils.equals(__DATE__.getNameCharArray(), name)
				|| CharArrayUtils.equals(__TIME__.getNameCharArray(), name);
	}

	@SuppressWarnings("nls")
	public Set<String> getSupportedFeatures() {
		if (sSupportedFeatures == null) {
			sSupportedFeatures = new HashSet<>();

			// C++98 features
			sSupportedFeatures.add("cxx_exceptions");
			sSupportedFeatures.add("cxx_rtti");

			// C++11 features
			// missing: cxx_access_control_sfinae (needs to be tested)
			sSupportedFeatures.add("cxx_alias_templates");
			sSupportedFeatures.add("cxx_alignas");
			sSupportedFeatures.add("cxx_alignof");
			sSupportedFeatures.add("cxx_atomic");
			sSupportedFeatures.add("cxx_attributes");
			sSupportedFeatures.add("cxx_auto_type");
			sSupportedFeatures.add("cxx_constexpr");
			sSupportedFeatures.add("cxx_decltype");
			// missing: cxx_decltype_incomplete_return_types (needs to be tested; see bug 324096)
			sSupportedFeatures.add("cxx_default_function_template_args");
			sSupportedFeatures.add("cxx_defaulted_functions");
			sSupportedFeatures.add("cxx_delegating_constructors");
			sSupportedFeatures.add("cxx_deleted_functions");
			sSupportedFeatures.add("cxx_explicit_conversions");
			sSupportedFeatures.add("cxx_generalized_initializers");
			// missing: cxx_implicit_moves (bug 327301)
			sSupportedFeatures.add("cxx_inheriting_constructors");
			sSupportedFeatures.add("cxx_inline_namespaces");
			sSupportedFeatures.add("cxx_lambdas");
			sSupportedFeatures.add("cxx_local_type_template_args");
			sSupportedFeatures.add("cxx_noexcept");
			sSupportedFeatures.add("cxx_nonstatic_member_init");
			sSupportedFeatures.add("cxx_nullptr");
			sSupportedFeatures.add("cxx_override_control");
			sSupportedFeatures.add("cxx_range_for");
			sSupportedFeatures.add("cxx_raw_string_literals");
			sSupportedFeatures.add("cxx_reference_qualified_functions");
			sSupportedFeatures.add("cxx_rvalue_references");
			sSupportedFeatures.add("cxx_static_assert");
			sSupportedFeatures.add("cxx_strong_enums");
			sSupportedFeatures.add("cxx_thread_local");
			sSupportedFeatures.add("cxx_trailing_return");
			sSupportedFeatures.add("cxx_unicode_literals");
			// missing: cxx_unrestricted_unions (bug 327299)
			sSupportedFeatures.add("cxx_user_literals");
			sSupportedFeatures.add("cxx_variadic_templates");

			// C++14 features
			// missing: cxx_aggregate_nsdmi
			// missing: cxx_binary_literals
			// missing: cxx_contextual_conversions
			// missing: cxx_decltype_auto (bug 408470)
			// missing: cxx_generic_lambdas
			// missing: cxx_init_captures (bug 413527)
			sSupportedFeatures.add("cxx_relaxed_constexpr");
			// missing: cxx_return_type_deduction (bug 408470)
			sSupportedFeatures.add("cxx_variable_templates");

			// C11 features
			sSupportedFeatures.add("c_alignas");
			sSupportedFeatures.add("c_alignof");
			// missing: c_atomic (bug 445297)
			// missing: c_generic_selections (bug 445296)
			// missing: c_static_assert (bug 445297)
			// missing: c_thread_local (bug 445297)

			// Type trait primitives
			// Whether support for these is activated depends on the scanner extension
			// configuration, so check it and report support accordingly.
			// Note also that having a keyword for it doesn't necessarily mean we fully
			// support it. For example, currently we have a keyword
			// GCCKeywords.cp__has_nothrown_assign, but we don't support evaluation of
			// this trait at the semantics level, so we don't report support for it.
			// missing: has_nothrow_assign
			// missing: has_nothrow_copy
			// missing: has_nothrow_constructor
			// missing: has_trivial_assign
			addTypeTraitPrimitive("has_trivial_copy", GCCKeywords.cp__has_trivial_copy);
			// missing: has_trivial_constructor
			// missing: has_trivial_destructor
			// missing: has_virtual_destructor
			addTypeTraitPrimitive("is_abstract", GCCKeywords.cp__is_abstract);
			addTypeTraitPrimitive("is_base_of", GCCKeywords.cp__is_base_of);
			addTypeTraitPrimitive("is_class", GCCKeywords.cp__is_class);
			addTypeTraitPrimitive("is_constructible", GCCKeywords.cp__is_constructible);
			// missing: is_convertible_to
			// missing: is_destructible
			addTypeTraitPrimitive("is_empty", GCCKeywords.cp__is_empty);
			addTypeTraitPrimitive("is_enum", GCCKeywords.cp__is_enum);
			addTypeTraitPrimitive("is_final", GCCKeywords.cp__is_final);
			// missing: is_interface_class
			// missing: is_literal
			// missing: is_nothrow_assignable
			// missing: is_nothrow_constructible
			// missing: is_nothrow_destructible
			addTypeTraitPrimitive("is_pod", GCCKeywords.cp__is_pod);
			addTypeTraitPrimitive("is_polymorphic", GCCKeywords.cp__is_polymorphic);
			addTypeTraitPrimitive("is_standard_layout", GCCKeywords.cp__is_standard_layout);
			addTypeTraitPrimitive("is_trivial", GCCKeywords.cp__is_trivial);
			// missing: is_trivially_assignable
			// missing: is_trivially_constructible
			addTypeTraitPrimitive("is_trivially_copyable", GCCKeywords.cp__is_trivially_copyable);
			addTypeTraitPrimitive("is_union", GCCKeywords.cp__is_union);
			addTypeTraitPrimitive("underlying_type", GCCKeywords.cp__underlying_type);
		}
		return sSupportedFeatures;
	}

	private void addTypeTraitPrimitive(String featureName, char[] keyword) {
		if (fKeywords.containsKey(keyword)) {
			sSupportedFeatures.add(featureName);
		}
	}
}
