BaseWorker.java

package edu.udel.cis.vsl.civl.transform.common;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import edu.udel.cis.vsl.abc.FrontEnd;
import edu.udel.cis.vsl.abc.ast.IF.AST;
import edu.udel.cis.vsl.abc.ast.IF.ASTFactory;
import edu.udel.cis.vsl.abc.ast.node.IF.ASTNode;
import edu.udel.cis.vsl.abc.ast.node.IF.ExternalDefinitionNode;
import edu.udel.cis.vsl.abc.ast.node.IF.IdentifierNode;
import edu.udel.cis.vsl.abc.ast.node.IF.NodeFactory;
import edu.udel.cis.vsl.abc.ast.node.IF.SequenceNode;
import edu.udel.cis.vsl.abc.ast.node.IF.declaration.VariableDeclarationNode;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.ExpressionNode;
import edu.udel.cis.vsl.abc.ast.node.IF.type.TypeNode;
import edu.udel.cis.vsl.abc.ast.type.IF.ArrayType;
import edu.udel.cis.vsl.abc.ast.type.IF.PointerType;
import edu.udel.cis.vsl.abc.ast.type.IF.StandardBasicType;
import edu.udel.cis.vsl.abc.ast.type.IF.StandardBasicType.BasicTypeKind;
import edu.udel.cis.vsl.abc.ast.type.IF.Type;
import edu.udel.cis.vsl.abc.parse.IF.CParser;
import edu.udel.cis.vsl.abc.parse.IF.OmpCParser;
import edu.udel.cis.vsl.abc.parse.IF.ParseException;
import edu.udel.cis.vsl.abc.parse.IF.ParseTree;
import edu.udel.cis.vsl.abc.preproc.IF.Preprocessor;
import edu.udel.cis.vsl.abc.preproc.IF.PreprocessorException;
import edu.udel.cis.vsl.abc.token.IF.CToken;
import edu.udel.cis.vsl.abc.token.IF.CTokenSource;
import edu.udel.cis.vsl.abc.token.IF.Formation;
import edu.udel.cis.vsl.abc.token.IF.Macro;
import edu.udel.cis.vsl.abc.token.IF.Source;
import edu.udel.cis.vsl.abc.token.IF.SourceFile;
import edu.udel.cis.vsl.abc.token.IF.SyntaxException;
import edu.udel.cis.vsl.abc.token.IF.TokenFactory;
import edu.udel.cis.vsl.abc.token.IF.TransformFormation;
import edu.udel.cis.vsl.abc.transform.IF.Transformer;
import edu.udel.cis.vsl.civl.config.IF.CIVLConstants;

/**
 * Object used to perform one transformation task. It is instantiated to carry
 * out one invocation of {@link CIVLBaseTransformer#transform(AST)}.
 * 
 * @author siegel
 */
public abstract class BaseWorker {

	/**
	 * The name of this transformer, e.g., "OMPtoCIVLTransformer". To be used in
	 * output such as error messages.
	 */
	protected String transformerName;

	/**
	 * The AST factory used by this transformer for all its AST needs.
	 */
	protected ASTFactory astFactory;

	/**
	 * The node factory used by this transformer; same as the node factory
	 * associated to the {@link #astFactory}.
	 */
	protected NodeFactory nodeFactory;

	/**
	 * The token facotry used by this transformer; same as the token factory
	 * used by the {@link #astFactory}.
	 */
	protected TokenFactory tokenFactory;

	/* ****************************** Constructor ************************** */

	protected BaseWorker(String transformerName, ASTFactory astFactory) {
		this.transformerName = transformerName;
		this.astFactory = astFactory;
		this.nodeFactory = astFactory.getNodeFactory();
		this.tokenFactory = astFactory.getTokenFactory();
	}

	/* *************************** Private Methods ************************* */

	/**
	 * Determines whether the given node is a leaf node, i.e., a node with no
	 * non-null children.
	 * 
	 * @param node
	 *            a non-null AST node
	 * @return true iff node is a leaf node
	 */
	private boolean isLeaf(ASTNode node) {
		for (ASTNode child : node.children()) {
			if (child != null)
				return false;
		}
		return true;
	}

	/**
	 * Finds the next node u after the given node, in DFS order, which satisfies
	 * (1) u is a leaf node, and (2) u contains "actual" source (i.e., not
	 * source generated by a transformer).
	 * 
	 * @param node
	 *            any AST node
	 * @return next leaf node whose first token is actual source, or null if
	 *         there is none
	 */
	private ASTNode nextRealNode(ASTNode node) {
		while (true) {
			node = node.nextDFS();
			if (node == null)
				break;
			if (isLeaf(node)) {
				Source source = node.getSource();

				if (source != null) {
					CToken token = source.getFirstToken();

					if (token != null) {
						Formation formation = token.getFormation();

						if (!(formation instanceof TransformFormation))
							break;
					}
				}
			}
		}
		return node;
	}

	/* ************************** Protected Methods ************************ */

	/**
	 * Transforms the AST. This is the method that will be invoked to implement
	 * {@link Transformer#transform(AST)}.
	 * 
	 * @param ast
	 *            the given AST to transform
	 * @return the transformed AST, which may or may not == the given one
	 * @throws SyntaxException
	 *             if some statically-detectable error is discovered in the
	 *             process of transsformation
	 */
	protected abstract AST transform(AST ast) throws SyntaxException;

	/**
	 * parses a certain CIVL library (which resides in the folder text/include)
	 * into an AST.
	 * 
	 * @param filename
	 *            the file name of the library, e.g., civlc.cvh, civlc-omp.cvh,
	 *            etc.
	 * @return the AST of the given library.
	 * @throws SyntaxException
	 */
	protected AST parseSystemLibrary(String filename) throws SyntaxException {
		FrontEnd frontEnd = new FrontEnd();
		Preprocessor preprocessor = frontEnd.getPreprocessor();
		CTokenSource tokenSource;
		ParseTree tree;

		try {
			tokenSource = preprocessor.outputTokenSource(
					new File[] { CIVLConstants.CIVL_INCLUDE_PATH },
					new File[0], new HashMap<String, Macro>(), filename);
			tree = frontEnd.getParser().parse(tokenSource);
		} catch (PreprocessorException | IOException | ParseException e) {
			return null;
		}
		return frontEnd.getASTBuilder().getTranslationUnit(tree);
	}

	/**
	 * Creates a new {@link Source} object to associate to AST nodes that are
	 * invented by this transformer worker.
	 * 
	 * @param method
	 *            any string which identifies the specific part of this
	 *            transformer responsible for creating the new content;
	 *            typically, the name of the method that created the new
	 *            context. This will appear in error message to help isolate the
	 *            source of the new content.
	 * @param text
	 *            the text to be shown for the source which should be some
	 *            informative message about the source
	 * @param tokenType
	 *            the integer code for the type of the token used to represent
	 *            the source; use one of the constants in {@link CParser} or
	 *            {@link OmpCParser}, for example, such as
	 *            {@link CParser#IDENTIFIER}.
	 * @return the new source object
	 */
	protected Source newSource(String method, int tokenType) {
		Formation formation = tokenFactory.newTransformFormation(
				transformerName, method);
		CToken token = tokenFactory.newCToken(tokenType, "inserted text",
				formation);
		Source source = tokenFactory.newSource(token);

		return source;
	}

	/**
	 * This method should be called after the transformer has completed its
	 * transformation; it finds all source objects (in node and the descendants
	 * of node) that were created by this transformer and adds more information
	 * to them. The new information includes the pretty-print textual
	 * representation of the content of that node (and its descendants), and the
	 * precise point in original actual source code where the new content was
	 * inserted.
	 * 
	 * @param node
	 *            a node in the AST being transformed; typically, the root node
	 */
	protected void completeSources(ASTNode node) {
		ASTNode postNode = nextRealNode(node);
		ASTNode preNode = null;

		for (; node != null; node = node.nextDFS()) {
			Source source = node.getSource();

			if (source != null) {
				CToken firstToken = source.getFirstToken();

				if (firstToken != null) {
					Formation formation = firstToken.getFormation();

					if (formation instanceof TransformFormation) {
						TransformFormation tf = (TransformFormation) formation;

						if (transformerName.equals(tf.getLastFile().getName())) {
							CToken preToken = preNode == null ? null : preNode
									.getSource().getLastToken();
							CToken postToken = postNode == null ? null
									: postNode.getSource().getFirstToken();
							String text = node.prettyRepresentation()
									.toString();

							if (text.length() > 20)
								text = text.substring(0, 18) + "...";
							tf.setPreToken(preToken);
							tf.setPostToken(postToken);
							firstToken.setText(text);
						} else {
							if (node == postNode) {
								preNode = postNode;
								postNode = nextRealNode(preNode);
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Creates an identifier node with a given name. The source information of
	 * the new node is automatically constructed using the method
	 * {@link #newSource(String, int)}.
	 * 
	 * @param name
	 *            The name of the identifier.
	 * @return the new identifier node.
	 */
	protected IdentifierNode identifier(String name) {
		return nodeFactory.newIdentifierNode(
				this.newSource("identifier " + name, CParser.IDENTIFIER), name);
	}

	/**
	 * Creates an identifier expression node with a given name. The source
	 * information of the new node is automatically constructed using the method
	 * {@link #newSource(String, int)}.
	 * 
	 * @param name
	 *            The name of the identifier.
	 * @return the new identifier expression node.
	 */
	protected ExpressionNode identifierExpression(String name) {
		Source source = this
				.newSource("identifier " + name, CParser.IDENTIFIER);

		return nodeFactory.newIdentifierExpressionNode(source,
				nodeFactory.newIdentifierNode(source, name));
	}

	/**
	 * Creates an identifier expression node with a given name.
	 * 
	 * @param source
	 *            The source information of the identifier.
	 * @param name
	 *            The name of the identifier.
	 * @return the new identifier expression node.
	 */
	protected ExpressionNode identifierExpression(Source source, String name) {
		return nodeFactory.newIdentifierExpressionNode(source,
				nodeFactory.newIdentifierNode(source, name));
	}

	/**
	 * Creates a variable declaration node with a given name of the specified
	 * type. The sources are created automatically through the method
	 * {@link #newSource(String, int)}.
	 * 
	 * @param name
	 *            The name of the variable
	 * @param type
	 *            The type of the variable
	 * @return the new variable declaration node
	 */
	protected VariableDeclarationNode variableDeclaration(String name,
			TypeNode type) {
		return nodeFactory.newVariableDeclarationNode(this.newSource(
				"variable declaration of " + name, CParser.DECLARATION), this
				.identifier(name), type);
	}

	/**
	 * Creates a variable declaration node with a given name of the specified
	 * type and initializer. The sources are created automatically through the
	 * method {@link #newSource(String, int)}.
	 * 
	 * @param name
	 *            The name of the variable
	 * @param type
	 *            The type of the variable
	 * @param init
	 *            The initializer of the variable
	 * @return the new variable declaration node.
	 */
	protected VariableDeclarationNode variableDeclaration(String name,
			TypeNode type, ExpressionNode init) {
		// String text = type.prettyRepresentation() + " " + name;
		// if (init != null)
		// text = text + " = " + init.prettyRepresentation();
		return nodeFactory.newVariableDeclarationNode(this.newSource(
				"variable declaration of " + name, CParser.DECLARATION), this
				.identifier(name), type, init);
	}

	/**
	 * Creates a constant node of <code>$here</code>, the source of which is
	 * generated automatically using {@link #newSource(String, int)}.
	 * 
	 * @return the new here node.
	 */
	protected ExpressionNode hereNode() {
		return nodeFactory.newHereNode(this.newSource("constant $here",
				CParser.HERE));
	}

	/**
	 * Creates a type node of void type, the source of which is generated
	 * automatically using {@link #newSource(String, int)}.
	 * 
	 * @return the new void type node.
	 */
	protected TypeNode voidType() {
		return nodeFactory.newVoidTypeNode(this.newSource("type void",
				CParser.VOID));
	}

	/**
	 * Creates a type node of a certain basic type kind, the source of which is
	 * generated automatically using {@link #newSource(String, int)}.
	 * 
	 * @param kind
	 *            the specified basic type kind
	 * @return the new basic type node.
	 */
	protected TypeNode basicType(BasicTypeKind kind) {
		String name = "";

		switch (kind) {
		case BOOL:
			name = "_Bool";
			break;
		case CHAR:
			name = "char";
			break;
		case DOUBLE:
		case DOUBLE_COMPLEX:
			name = "double";
			break;
		case FLOAT:
		case FLOAT_COMPLEX:
			name = "float";
			break;
		case INT:
			name = "int";
			break;
		case LONG:
			name = "long";
			break;
		case LONG_DOUBLE:
			name = "long double";
			break;
		case LONG_DOUBLE_COMPLEX:
			name = "long double";
			break;
		case LONG_LONG:
			name = "long long";
			break;
		case REAL:
			name = "real";
			break;
		case SHORT:
			name = "short";
			break;
		case SIGNED_CHAR:
			name = "signed char";
			break;
		case UNSIGNED:
			name = "unsigned";
			break;
		case UNSIGNED_CHAR:
			name = "unsigned char";
			break;
		case UNSIGNED_LONG:
			name = "unsigned long";
			break;
		case UNSIGNED_LONG_LONG:
			name = "unsigned long long";
			break;
		case UNSIGNED_SHORT:
			name = "unsigned short";
		default:
		}
		return this.nodeFactory.newBasicTypeNode(
				this.newSource("type " + name, CParser.TYPE), kind);
	}

	/**
	 * Creates a type node of a given type, the source of which is generated
	 * automatically using {@link #newSource(String, int)}.
	 * 
	 * @param type
	 *            the specified type
	 * @return the new type node.
	 */
	protected TypeNode typeNode(Type type) {
		Source source = this.newSource("type " + type, CParser.TYPE);

		switch (type.kind()) {
		case VOID:
			return nodeFactory.newVoidTypeNode(source);
		case BASIC:
			return nodeFactory.newBasicTypeNode(source,
					((StandardBasicType) type).getBasicTypeKind());
		case OTHER_INTEGER:
			return nodeFactory.newBasicTypeNode(source, BasicTypeKind.INT);
		case ARRAY:
			return nodeFactory.newArrayTypeNode(source,
					this.typeNode(((ArrayType) type).getElementType()),
					((ArrayType) type).getVariableSize().copy());
		case POINTER:
			return nodeFactory.newPointerTypeNode(source,
					this.typeNode(((PointerType) type).referencedType()));
		default:
		}
		return null;
	}

	/**
	 * Creates a type node of a given type, with the given source.
	 * 
	 * @param source
	 *            The source of the type node
	 * @param type
	 *            the specified type
	 * @return the new type node
	 */
	protected TypeNode typeNode(Source source, Type type) {
		switch (type.kind()) {
		case VOID:
			return nodeFactory.newVoidTypeNode(source);
		case BASIC:
			return nodeFactory.newBasicTypeNode(source,
					((StandardBasicType) type).getBasicTypeKind());
		case OTHER_INTEGER:
			return nodeFactory.newBasicTypeNode(source, BasicTypeKind.INT);
		case ARRAY:
			return nodeFactory.newArrayTypeNode(source,
					this.typeNode(source, ((ArrayType) type).getElementType()),
					((ArrayType) type).getVariableSize().copy());
		case POINTER:
			return nodeFactory.newPointerTypeNode(source, this.typeNode(source,
					((PointerType) type).referencedType()));
		default:
		}
		return null;
	}

	/**
	 * Creates a boolean constant node (either <code>$true</code> or
	 * <code>$false</code>), the source of which is generated automatically
	 * using {@link #newSource(String, int)}.
	 * 
	 * @param value
	 *            The value of the boolean constant
	 * @return the new boolean constant node
	 */
	protected ExpressionNode booleanConstant(boolean value) {
		String method = value ? "constant $true" : "constant $false";
		int tokenType = value ? CParser.TRUE : CParser.FALSE;

		return nodeFactory.newBooleanConstantNode(
				this.newSource(method, tokenType), value);
	}

	/**
	 * Creates an integer constant node of the specified value, the source of
	 * which is generated automatically using {@link #newSource(String, int)}.
	 * 
	 * @param value
	 *            The value of the integer constant
	 * @return the new integer constant node
	 */
	protected ExpressionNode integerConstant(int value) throws SyntaxException {
		return nodeFactory.newIntegerConstantNode(
				this.newSource("constant " + value, CParser.INTEGER_CONSTANT),
				Integer.toString(value));
	}

	/**
	 * Combines two ASTs into one, assuming that there are no name conflicts.
	 * 
	 * @param first
	 *            the first AST
	 * @param second
	 *            the second AST
	 * @return
	 * @throws SyntaxException
	 */
	protected AST combineASTs(AST first, AST second) throws SyntaxException {
		SequenceNode<ExternalDefinitionNode> rootNode;
		List<ExternalDefinitionNode> firstNodes = new ArrayList<>(), secondNodes = new ArrayList<>(), allNodes = new ArrayList<>();
		List<SourceFile> sourceFiles = new ArrayList<>();

		for (ExternalDefinitionNode child : first.getRootNode()) {
			if (child != null)
				firstNodes.add(child.copy());
		}
		for (ExternalDefinitionNode child : second.getRootNode()) {
			// avoid identical nodes introduced by same "includes"
			if (child != null && !this.existNode(firstNodes, child))
				secondNodes.add(child.copy());
		}
		allNodes.addAll(firstNodes);
		allNodes.addAll(secondNodes);
		sourceFiles.addAll(first.getSourceFiles());
		sourceFiles.addAll(second.getSourceFiles());
		rootNode = this.nodeFactory.newSequenceNode(first.getRootNode()
				.getSource(), "Translation Unit", allNodes);
		return this.astFactory.newAST(rootNode, sourceFiles);
	}

	/**
	 * Checks if a list of nodes contains an identical node of a given node.
	 * 
	 * @param nodes
	 *            the list of nodes
	 * @param theNode
	 *            the specified node
	 * @return true iff the list of nodes contains an identical node of the
	 *         specified node.
	 */
	private boolean existNode(List<ExternalDefinitionNode> nodes,
			ExternalDefinitionNode theNode) {
		for (ASTNode node : nodes) {
			if (node.diff(theNode) == null)
				return true;
		}
		return false;
	}
}