blob: a38f7f7f8ab344270ce876e43eaea22f275b2204 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 University of Illinois at Urbana-Champaign 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:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.photran.internal.core.preprocessor.c;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.parser.c.GCCScannerExtensionConfiguration;
import org.eclipse.cdt.core.parser.CodeReader;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfoProvider;
import org.eclipse.cdt.core.parser.NullLogService;
import org.eclipse.cdt.core.parser.ParserLanguage;
import org.eclipse.cdt.core.parser.ScannerInfo;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
/**
* A wrapper class for the CPreprocessor in order to make
* functionality easier.
*
* @author Matthew Michelotti
*/
public class CppHelper {
/**
* An option.
* When it is true, CppHelper will use CPreprocessor.nextTokenRaw() to
* get tokens. When it is false, CppHelper will use
* CPreprocessor.nextToken() to get tokens. Currently, the
* implementation of CPreprocessor is such that useRawTokens should
* always be true, but this option is here in case a switch needs to
* be made at some point.
*/
private boolean useRawTokens = true;
/**The CPreprocessor object to get tokens from*/
private CPreprocessor cpp;
/**True iff all Tokens have been obtained from the CPreprocessor*/
private boolean finished = false;
/**
* Constructs a CPreprocessor to read tokens from the given file.
* Included files are taken relative to this file
* or the includeSearchPaths.<br/>
* NOTE: If more control over the include search paths is desired,
* consider making a new CppHelper constructor using the class
* ExtendedScannerInfo instead of ScannerInfo.
* @param filename - name of file that input stream belongs to
* @param in - input stream to read file from
* @param includeSearchPaths - file paths to search in for included files
* @throws IOException
*/
public CppHelper(String filename, Reader in, String[] includeSearchPaths)
throws IOException
{
//NOTE: it is important that the CodeReader gets an ABSOLUTE file path
cpp = new CPreprocessor(new ReaderBasedCodeReader(filename, in),
new ScannerInfo(null, includeSearchPaths), ParserLanguage.C,
new NullLogService(), new GCCScannerExtensionConfiguration(),
FileCodeReaderFactory.getInstance());
}
/**
* Constructs a CPreprocessor to read tokens from the given file.
* Included files are taken relative to this file.
* @param file - IFile of the input stream belongs to
* @param filename - name of file that input stream belongs to
* @param in - input stream to read file from
* @throws IOException
* NOTE: @param file could be null (if the opened file is not in the workspace).
* Also, @param in could be different than @param file, if for example there
* are un-saved changes in the editor.
*/
public CppHelper(IResource infoProvider, String filename, Reader in) throws IOException {
//NOTE: it is important that the CodeReader gets an ABSOLUTE file path
if (filename == null)
filename = ""; //$NON-NLS-1$
IScannerInfo scanInfo = new ScannerInfo();
if(infoProvider != null)
{
filename = infoProvider.getFullPath().toString();
IProject project = infoProvider.getProject();
IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(project);
if (provider != null) {
IScannerInfo buildScanInfo = provider.getScannerInformation(infoProvider);
if (buildScanInfo != null)
scanInfo = buildScanInfo;
}
}
//Map<String, String> temp = scanInfo.getDefinedSymbols();
cpp = new CPreprocessor(new ReaderBasedCodeReader(filename, in),
new ScannerInfo(scanInfo.getDefinedSymbols(), scanInfo.getIncludePaths()), ParserLanguage.C, new NullLogService(),
new GCCScannerExtensionConfiguration(), FileCodeReaderFactory.getInstance());
}
/**
* Constructs a CPreprocessor to read tokens from the given file.
* Included files are taken relative to this file.
* @param file - file to read tokens from
* @throws IOException
*/
public CppHelper(File file) throws IOException {
//NOTE: it is important that the CodeReader gets an ABSOLUTE file path
cpp = new CPreprocessor(new CodeReader(file.getAbsolutePath()),
new ScannerInfo(), ParserLanguage.C, new NullLogService(),
new GCCScannerExtensionConfiguration(), FileCodeReaderFactory.getInstance());
}
/**
* Constructs a CPreprocessor to read tokens from the given file.
* Included files are taken relative to this file.
* @param filename - name of file to read tokens from
* @throws IOException
*/
public CppHelper(String filename) throws IOException {
this(new File(filename));
}
/**
* Obtain the next token from the CPreprocessor
* @return next token, or null if there are no more tokens
* @throws OffsetLimitReachedException
*/
public IToken nextToken() throws OffsetLimitReachedException {
if(finished) return null;
if(useRawTokens) {
IToken next = cpp.nextTokenRaw();
if(next.getType() == IToken.tEND_OF_INPUT) finished = true;
return next;
}
else {
IToken next = null;
try {next = cpp.nextToken();}
catch (EndOfFileException e) {
finished = true;
return null;
}
return next;
}
}
/**
* Call nextToken() repeatedly until the CPreprocessor is out of tokens.
* @return The first token obtained. You can get to the rest of the
* tokens by using IToken.getNext()
* @throws OffsetLimitReachedException
*/
public IToken getRemainingTokens() throws OffsetLimitReachedException {
IToken first = nextToken();
IToken t = first;
while(t != null) t = nextToken();
return first;
}
/**
* Find the highest ancestor of a given token. i.e., call
* t.getParent() repeatedly and return the last non-null value.
* May return the input token.
* @param t - token to find ancestor of
* @param ignoreCarriageReturnAncestors - just returns t
* if its ancestor is "\r\n"
* @return highest ancestor of the given token
*/
public static IToken getAncestor(IToken t, boolean ignoreCarriageReturnAncestors) {
IToken lastParent = t;
IToken parent = lastParent.getParent();
while(parent != null) {
lastParent = parent;
parent = lastParent.getParent();
}
if(ignoreCarriageReturnAncestors) {
if(t.getType() == Lexer.tNEWLINE && lastParent.getImage().equals("\r\n") //$NON-NLS-1$
&& Arrays.equals(t.getCharPrecedingWhiteSpace(), lastParent.getCharPrecedingWhiteSpace()))
{
return t;
}
}
return lastParent;
}
/**
* Same as getAncestor(IToken, boolean), but assumes
* ignoreCaradgeReturnAncestors is false.
* @param t - token to find ancestor of
* @return highest ancestor of the given token
*/
public static IToken getAncestor(IToken t) {
return getAncestor(t, false);
}
/**
* find the first include directive which is an ancestor of this
* token (does not consider the input token itself)
* @param t - token to find an include directive parent of
* @return the found include directive token, or null if none was found
*/
public static IToken getIncludeParent(IToken t) {
for(IToken p = t.getParent(); p != null; p = p.getParent()) {
if(p.getIncludeFile() != null) return p;
}
return null;
}
/**@param t - token to get image of
* @return the image of the token (without white-space)*/
public static String getImage(IToken t) {
if(t.getType() == Lexer.tNEWLINE) return "\n"; //$NON-NLS-1$
else return t.getImage();
}
/**@param t - token
* @return the white space before the token*/
public static String getPreWhiteSpace(IToken t) {
return t.getPrecedingWhiteSpace();
}
/**@param t - token to get whitespace of
* @return the length of the whitespace before the token*/
public static int getPreWhiteSpaceLength(IToken t) {
return t.getCharPrecedingWhiteSpace().length;
}
/**@param t - token to get image of
* @return the length of the token image (without white-space)*/
public static int getImageLength(IToken t) {
if(t.getType() == Lexer.tNEWLINE) return 1;
else return t.getCharImage().length;
}
/**@param t - token to get full image of
* @return full image of the given token, including whitespace*/
public static String getFullImage(IToken t) {
return getPreWhiteSpace(t) + getImage(t);
}
/**@param t - token to get length of
* @return the length of the full token image, including whitespace*/
public static int getFullImageLength(IToken t) {
return getPreWhiteSpaceLength(t) + getImageLength(t);
}
/**
* Get a String describing the details of Token. Each parent in
* the hierarchy will be listed, separated by tokenSeparator.
* The preceding white space and image of each token in the
* hierarchy will be listed, separated by tokenSeparator.
* "\n" is replaced with "\\n", and "\r" is replaced with "\\r",
* so assuming spaceSeparator and tokenSeparator don't have newline
* characters in them, this String will be all on one line.
* Note that this method may not be thread-safe even though it is static,
* since it accesses a static variable.
* @param t - token to describe
* @param spaceSeparator - String that should be used to separate spaces
* @param tokenSeparator - String that should be used to separate tokens
* @param printTypes - true iff token types should be printed for the
* token t and its ancestors. The type will be placed before the
* pre-whitespace of each token, followed by a spaceSeparator.
* @return String describing the token and its parents
*/
public static String getTokenDetails(IToken t, String spaceSeparator,
String tokenSeparator, boolean printTypes)
{
StringBuffer buffer = new StringBuffer(200);
while(true) {
if(printTypes) {
buffer.append(TokenTypeTranslator.typeToString(t.getType()));
buffer.append(spaceSeparator);
}
appendNeatString(buffer, t.getCharPrecedingWhiteSpace());
buffer.append(spaceSeparator);
if(t.getType() == Lexer.tNEWLINE) buffer.append("\\n"); //$NON-NLS-1$
else appendNeatString(buffer, t.getCharImage());
t = t.getParent();
if(t == null) break;
buffer.append(tokenSeparator);
}
return buffer.toString();
}
/**
* Reproduce the original source code from a sequence of
* tokens, and return as a string.
* @param ignoreCarriageReturnAncestors - if a token has
* "\r\n" as its ancestor, ignore the ancestor
* @param startToken - a token, which contains a series of
* tokens after it that can be obtained using
* IToken.getNext()
* @return the original source code
*/
public static String reproduceSourceCode(IToken startToken, boolean ignoreCarriageReturnAncestors) {
StringBuffer buffer = new StringBuffer(4096);
IToken lastAncestor = null;
for(IToken t = startToken; t != null; t = t.getNext()) {
IToken ancestor = CppHelper.getAncestor(t, ignoreCarriageReturnAncestors);
if(ancestor != lastAncestor) {
buffer.append(CppHelper.getFullImage(ancestor));
lastAncestor = ancestor;
}
}
return buffer.toString();
}
/**
* Same as reproduceSourceCode(IToken, boolean), except
* ignoreCarriageReturnAncestors is assumed to be false.
* @param startToken - a token, which contains a series of
* tokens after it that can be obtained using
* IToken.getNext()
* @return the original source code
*/
public static String reproduceSourceCode(IToken startToken) {
return reproduceSourceCode(startToken, false);
}
/**
* Helper function for getTokenDetails.
* This will add characters to a buffer, but it will first
* replace "\n" with "\\n" and "\r" with "\\r"
* @param buffer - StringBuffer to add characters to
* @param chars - characters to add to buffer
*/
private static void appendNeatString(StringBuffer buffer, char[] chars) {
for(int i = 0; i < chars.length; i++) {
switch(chars[i]) {
case('\n'): buffer.append("\\n"); break; //$NON-NLS-1$
case('\r'): buffer.append("\\r"); break; //$NON-NLS-1$
default: buffer.append(chars[i]); break;
}
}
}
}