PreprocessorUtils.java

package edu.udel.cis.vsl.abc.front.c.preproc;

import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ABSTRACT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ALIGNAS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ALIGNOF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ASSIGNS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ATOMIC;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.AUTO;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.BIG_O;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.BOOL;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.BREAK;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CALLS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CASE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CHAR;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CHOOSE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CIVLATOMIC;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CIVLFOR;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.COMPLEX;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CONST;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CONTIN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.CONTINUE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DEFAULT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DEFINE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DEFINED;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DEPENDS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DERIV;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DEVICE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DO;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DOMAIN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.DOUBLE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ELIF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ELSE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ENDIF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ENSURES;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ENUM;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.ERROR;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.EXISTS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.EXTERN;
//import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.FALSE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.FATOMIC;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.FLOAT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.FOR;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.FORALL;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.GENERIC;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.GLOBAL;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.GOTO;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.GUARD;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.HERE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.IDENTIFIER;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.IF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.IFDEF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.IFNDEF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.IMAGINARY;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.INCLUDE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.INLINE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.INPUT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.INT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.INVARIANT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.LAMBDA;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.LINE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.LONG;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.NORETURN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.OUTPUT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.PARFOR;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.PRAGMA;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.PROCNULL;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.RANGE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.READS;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.REAL;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.REGISTER;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.REQUIRES;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.RESTRICT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.RESULT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.RETURN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SCOPEOF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SELF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SHARED;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SHORT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SIGNED;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SIZEOF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SPAWN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.STATIC;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.STATICASSERT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.STRUCT;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SWITCH;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.SYSTEM;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.THREADLOCAL;
//import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.TRUE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.TYPEDEF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.TYPEOF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.UNDEF;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.UNIFORM;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.UNION;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.UNSIGNED;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.VOID;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.VOLATILE;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.WHEN;
import static edu.udel.cis.vsl.abc.front.c.preproc.PreprocessorParser.WHILE;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.Map.Entry;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenSource;

import edu.udel.cis.vsl.abc.front.IF.PreprocessorException;
import edu.udel.cis.vsl.abc.front.IF.PreprocessorExpressionException;
import edu.udel.cis.vsl.abc.util.IF.ANTLRUtils;

public class PreprocessorUtils {

	private static int identifierMinIndex, identifierMaxIndex;
	private static boolean[] identifierPredicate = initIdentifierPredicate();

	private static boolean[] initIdentifierPredicate() {
		int[] identifierIndexes = new int[]{ABSTRACT, ALIGNAS, ALIGNOF, ASSIGNS,
				ATOMIC, AUTO, BIG_O, BOOL, BREAK, CALLS, CASE, CHAR, CHOOSE,
				CIVLATOMIC, CIVLFOR, COMPLEX, CONST, CONTIN, CONTINUE, DEFAULT,
				DEFINE, DEFINED, DEPENDS, DERIV, DEVICE, DO, DOMAIN, DOUBLE,
				ELIF, ELSE, ENDIF, ENSURES, ENUM, ERROR, EXISTS, EXTERN,
				FATOMIC, FLOAT, FOR, FORALL, GENERIC, GLOBAL, GOTO, GUARD, HERE,
				IF, IFDEF, IFNDEF, IMAGINARY, INCLUDE, INLINE, INPUT, INT,
				INVARIANT, LAMBDA, LINE, LONG, NORETURN, OUTPUT, PARFOR, PRAGMA,
				PROCNULL, RANGE, READS, REAL, REGISTER, REQUIRES, RESTRICT,
				RESULT, RETURN, SCOPEOF, SELF, SHARED, SHORT, SIGNED, SIZEOF,
				SPAWN, STATIC, STATICASSERT, STRUCT, SWITCH, SYSTEM,
				THREADLOCAL, TYPEDEF, TYPEOF, UNDEF, UNIFORM, UNION, UNSIGNED,
				VOID, VOLATILE, WHEN, WHILE};
		boolean[] result;
		int length;
		int min = identifierIndexes[0], max = identifierIndexes[0];

		for (int index : identifierIndexes) {
			if (index < min)
				min = index;
			if (index > max)
				max = index;
		}
		length = max - min + 1;
		result = new boolean[length];
		for (int index : identifierIndexes)
			result[index - min] = true;
		identifierMinIndex = min;
		identifierMaxIndex = max;
		return result;
	}

	/**
	 * Is the token a preprocessor identifier. That would be any C identifier
	 * (type IDENTIFIER) or any keyword.
	 * 
	 * @param token
	 *            any token
	 * @return true iff the token's type is either IDENTIFIER or any of the
	 *         types in the list of C keywords.
	 */
	public static boolean isIdentifier(Token token) {
		int type = token.getType();

		if (type == PreprocessorParser.IDENTIFIER)
			return true;
		return type >= identifierMinIndex && type <= identifierMaxIndex
				&& identifierPredicate[type - identifierMinIndex];
	}

	public static boolean isPpNumber(Token token) {
		int type = token.getType();

		return type == PreprocessorParser.INTEGER_CONSTANT
				|| type == PreprocessorParser.FLOATING_CONSTANT
				|| type == PreprocessorParser.PP_NUMBER;
	}

	/**
	 * Is the preprocessor token considered a white space token? Spaces, tabs,
	 * newlines, comments are all white space.
	 * 
	 * @param token
	 *            any token defined in the PreprocessorParser
	 * @return true iff token is a form of white space
	 */
	public static boolean isWhiteSpace(Token token) {
		int type = token.getType();

		return type == PreprocessorParser.WS
				|| type == PreprocessorParser.COMMENT
				|| type == PreprocessorParser.NEWLINE;
	}

	/**
	 * This convenience method transforms a TokenSource by filtering out the
	 * white space tokens. The TokenSource returned is equivalent to the given
	 * TokenSource, except that all white space tokens (spaces, tabs, newlines)
	 * have been removed.
	 * 
	 * @param oldSource
	 *            a token source that might have white space
	 * @return a new token source equivalent to old but with white space tokens
	 *         removed
	 */
	public static TokenSource filterWhiteSpace(final TokenSource oldSource) {
		TokenSource newSource = new TokenSource() {
			@Override
			public String getSourceName() {
				return oldSource.getSourceName();
			}

			@Override
			public Token nextToken() {
				while (true) {
					Token token = oldSource.nextToken();

					if (!isWhiteSpace(token))
						return token;
				}
			}
		};
		return newSource;
	}

	public static Integer convertStringToInt(String text)
			throws PreprocessorExpressionException {
		String stripped, root;
		// String suffix;
		int length = text.length();
		Integer result;

		while (length >= 1) {
			char c = text.charAt(length - 1);

			if (c != 'U' && c != 'u' && c != 'l' && c != 'L')
				break;
			length--;
		}
		stripped = text.substring(0, length);
		// TODO: do anything with suffix?
		// suffix = text.substring(length);
		try {
			if (stripped.startsWith("0")) {
				if (stripped.startsWith("0x") || stripped.startsWith("0X")) {
					// hexadecimal
					root = stripped.substring(2);
					result = Integer.parseInt(root, 16);
				} else {
					// octal
					result = Integer.parseInt(stripped, 8);
				}
			} else {
				// decimal
				result = Integer.valueOf(stripped);
			}
		} catch (NumberFormatException e) {
			throw new PreprocessorExpressionException(
					"Unable to extract integer value from " + text + ":\n" + e);
		}
		return result;
	}

	public static Double convertStringToDouble(String text)
			throws PreprocessorExpressionException {
		// TODO: fix
		return Double.valueOf(text);
	}

	/**
	 * If the token has type which is one of the preprocessor keywords (e.g.,
	 * DEFINE, which is the type of the token "define"), it is changed to the
	 * IDENTIFIER type. Otherwise, the token is unchanged.
	 * 
	 * @param token
	 *            any preprocessing token (including whitespace)
	 */
	public static void convertPreprocessorIdentifiers(Token token) {
		int tokenType = token.getType();

		switch (tokenType) {
			case DEFINE :
			case DEFINED :
			case ELIF :
			case ENDIF :
			case ERROR :
			case IFDEF :
			case IFNDEF :
			case INCLUDE :
			case LINE :
			case PRAGMA :
			case UNDEF :
				token.setType(IDENTIFIER);
			default :
		}
	}

	/**
	 * Prints the stream of tokens emanating from a token source. Used mainly
	 * for debugging. Uses the tokens' "toString" method.
	 * 
	 * @param out
	 *            a print stream to which the output is sent
	 * @param source
	 *            any instance of TokenSource
	 * @throws PreprocessorException
	 *             if any exception is thrown while printing a token or getting
	 *             the next token. CommonToken's toString method can throw all
	 *             manner of exceptions
	 */
	public static void printTokenSource(PrintStream out, TokenSource source)
			throws PreprocessorException {
		try {
			CommonToken token;

			do {
				token = (CommonToken) source.nextToken();
				out.println(token.toString());
				out.flush();
			} while (token.getType() != PreprocessorParser.EOF);
		} catch (RuntimeException e) {
			e.printStackTrace(System.err);
			throw new PreprocessorException(e.toString());
		}
	}

	public static void sourceTokenSource(PrintStream out, TokenSource source)
			throws PreprocessorException {
		try {
			CommonToken token;
			int type;

			while (true) {
				token = (CommonToken) source.nextToken();
				type = token.getType();
				if (type == PreprocessorParser.EOF)
					break;
				if (type == PreprocessorParser.COMMENT)
					out.print(" ");
				else
					out.print(token.getText());
				out.flush();
			}
		} catch (RuntimeException e) {
			e.printStackTrace(out);
			throw new PreprocessorException(e.getMessage());
		}
	}

	public static void source(PrintStream out, File file)
			throws PreprocessorException {
		try {
			ANTLRUtils.source(out, file);
		} catch (IOException e) {
			e.printStackTrace(out);
			throw new PreprocessorException(e.toString());
		}
	}

	public static CharStream newFilteredCharStreamFromFile(File file)
			throws IOException {
		InputStream inputStream = new FileInputStream(file);
		CharStream charStream = new CommonCharacterStream(
				file.getAbsolutePath(), inputStream);
		CharStream filteredStream = new FilteredStream(charStream);

		return filteredStream;
	}

	/**
	 * Creates new filtered character stream from the specified internal
	 * resource. Used to read files that are stored inside the class path
	 * (including inside a jar file).
	 * 
	 * @param name
	 *            a name to assign to the stream; used only for reporting
	 *            errors, referring to the stream, etc.
	 * @param resource
	 *            the actual name of the resource, which is absolute path
	 *            relative to the class path
	 * @return the character stream or <code>null</code> if the resource could
	 *         not be found
	 * @throws IOException
	 *             if something goes wrong reading from the stream
	 */
	public static CharStream newFilteredCharStreamFromResource(String name,
			String resource) throws IOException {
		InputStream inputStream = PreprocessorUtils.class
				.getResourceAsStream(resource);

		if (inputStream == null)
			return null;

		CharStream charStream = new CommonCharacterStream(name, inputStream);
		CharStream filteredStream = new FilteredStream(charStream);

		return filteredStream;
	}

	/**
	 * Find the file with the given name by looking through the directories in
	 * the given list. Go through list from first to last. Returns first
	 * instance found.
	 * 
	 * Note: the filename may itself containing directory structure, e.g.,
	 * "sys/stdio.h".
	 * 
	 * @param paths
	 *            list of directories to search
	 * @param filename
	 *            name of file
	 * @return file named filename, or null if not found
	 */
	public static File findFile(File[] paths, String filename) {
		for (File path : paths) {
			File result = new File(path, filename);

			if (result.isFile())
				return result;
		}
		return null;
	}

	/**
	 * Converts a macro map to an ANTLR character stream. The macro map
	 * specifies macros as key-value pairs, where the key is the name of the
	 * macro (and possible formal parameter list, if the macro is a
	 * function-like macro) and the value is the body. The character stream
	 * return follows the C preprocessor format: a sequence of
	 * newline-terminated lines of the form "#define NAME BODY"
	 * 
	 * @param macroMap
	 *            map from macro names to bodies
	 * @return character stream defining macros in the C preprocessor format
	 */
	public static CharStream macroMapToCharStream(
			Map<String, String> macroMap) {
		StringBuffer sb = new StringBuffer();

		for (Entry<String, String> entry : macroMap.entrySet()) {
			sb.append("#define ");
			sb.append(entry.getKey());
			sb.append(" ");
			sb.append(entry.getValue());
			sb.append(System.lineSeparator());
		}

		int n = sb.length();
		char[] charArray = new char[n];

		sb.getChars(0, n, charArray, 0);
		return new ANTLRStringStream(charArray, n);
	}

}