IOTransformer.java

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

import java.util.ArrayList;
import java.util.List;

import edu.udel.cis.vsl.abc.ABCUnsupportedException;
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.IdentifierNode;
import edu.udel.cis.vsl.abc.ast.node.IF.declaration.FunctionDefinitionNode;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.ExpressionNode;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.ExpressionNode.ExpressionKind;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.FunctionCallNode;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.IdentifierExpressionNode;
import edu.udel.cis.vsl.abc.ast.node.IF.expression.StringLiteralNode;
import edu.udel.cis.vsl.abc.ast.node.IF.statement.BlockItemNode;
import edu.udel.cis.vsl.abc.ast.node.IF.statement.CompoundStatementNode;
import edu.udel.cis.vsl.abc.ast.node.IF.statement.ExpressionStatementNode;
import edu.udel.cis.vsl.abc.ast.node.IF.statement.ReturnNode;
import edu.udel.cis.vsl.abc.ast.value.IF.StringValue;
import edu.udel.cis.vsl.abc.token.IF.Source;
import edu.udel.cis.vsl.abc.token.IF.SyntaxException;
import edu.udel.cis.vsl.abc.transform.IF.BaseTransformer;

/**
 * The IO transformer transforms<br>
 * <ul>
 * <li>all function calls printf(...) into frpintf(stdout, ...)</li>
 * <li>all function calls scanf(...) into fscanf(stdin, ...)</li>
 * <li>all function calls fopen(...) into $fopen(...)</li>
 * </ul>
 * 
 * @author zmanchun
 * 
 */
public class IOTransformer extends BaseTransformer {

	public static String CODE = "io";
	public static String LONG_NAME = "IOTransformer";
	public static String SHORT_DESCRIPTION = "transforms C program with IO to CIVL-C";

	public static String PRINTF = "printf";
	public static String FPRINTF = "fprintf";
	public static String SCANF = "scanf";
	public static String FSCANF = "fscanf";
	public static String STD_OUT = "stdout";
	public static String STD_IN = "stdin";
	public static String FOPEN = "fopen";
	public static String FOPEN_NEW = "$fopen";
	public static String CIVL_FILE_SYSTEM = "CIVL_filesystem";
	public static String CIVL_FILE_MODE_R = "CIVL_FILE_MODE_R";// r
	public static String CIVL_FILE_MODE_W = "CIVL_FILE_MODE_W";// w
	public static String CIVL_FILE_MODE_WX = "CIVL_FILE_MODE_WX";// wx
	public static String CIVL_FILE_MODE_A = "CIVL_FILE_MODE_A";// a
	public static String CIVL_FILE_MODE_RB = "CIVL_FILE_MODE_RB";// rb
	public static String CIVL_FILE_MODE_WB = "CIVL_FILE_MODE_WB";// wb
	public static String CIVL_FILE_MODE_WBX = "CIVL_FILE_MODE_WBX";// wbx
	public static String CIVL_FILE_MODE_AB = "CIVL_FILE_MODE_AB";// ab
	public static String CIVL_FILE_MODE_RP = "CIVL_FILE_MODE_RP";// r+
	public static String CIVL_FILE_MODE_WP = "CIVL_FILE_MODE_WP";// w+
	public static String CIVL_FILE_MODE_WPX = "CIVL_FILE_MODE_WPX";// w+x
	public static String CIVL_FILE_MODE_AP = "CIVL_FILE_MODE_AP";// a+
	public static String CIVL_FILE_MODE_RPB = "CIVL_FILE_MODE_RPB";// r+b or rb+
	public static String CIVL_FILE_MODE_WPB = "CIVL_FILE_MODE_WPB";// w+b or wb+
	public static String CIVL_FILE_MODE_WPBX = "CIVL_FILE_MODE_WPBX";// w+bx or
																		// wb+x
	public static String CIVL_FILE_MODE_APB = "CIVL_FILE_MODE_APB";// a+b or ab+
	public static String FOPEN_R = "r";
	public static String FOPEN_W = "w";
	public static String FOPEN_WX = "wx";
	public static String FOPEN_A = "a";
	public static String FOPEN_RB = "rb";
	public static String FOPEN_WB = "wb";
	public static String FOPEN_WBX = "wbx";
	public static String FOPEN_AB = "ab";
	public static String FOPEN_RP = "r+";
	public static String FOPEN_WP = "w+";
	public static String FOPEN_WPX = "w+x";
	public static String FOPEN_AP = "a+";
	public static String FOPEN_RPB = "r+b";
	public static String FOPEN_RBP = "rb+";
	public static String FOPEN_WPB = "w+b";
	public static String FOPEN_WBP = "wb+";
	public static String FOPEN_WPBX = "w+bx";
	public static String FOPEN_WBPX = "wb+x";
	public static String FOPEN_APB = "a+b";
	public static String FOPEN_ABP = "ab+";

	public static String FILESYSTEM_DESTROY = "$filesystem_destroy";
	public static String CIVL_FILESYSTEM = "CIVL_filesystem";

	public IOTransformer(ASTFactory astFactory) {
		super(IOTransformer.CODE, IOTransformer.LONG_NAME,
				IOTransformer.SHORT_DESCRIPTION, astFactory);
	}

	@Override
	public AST transform(AST unit) throws SyntaxException {
		ASTNode rootNode = unit.getRootNode();

		assert this.astFactory == unit.getASTFactory();
		assert this.nodeFactory == astFactory.getNodeFactory();
		unit.release();
		this.renameFunctionCalls(rootNode);
		this.processFreeCall(rootNode);
		return astFactory.newTranslationUnit(rootNode);
	}

	private void processFreeCall(ASTNode node) {
		int numChildren = node.numChildren();

		for (int i = 0; i < numChildren; i++) {
			ASTNode child = node.child(i);

			if (child instanceof FunctionDefinitionNode) {
				FunctionDefinitionNode function = (FunctionDefinitionNode) child;
				String functionName = function.getName();

				if (functionName.equals("main")) {
					ExpressionStatementNode functionCallStatement;
					FunctionCallNode functionCall;
					List<ExpressionNode> arguments = new ArrayList<>(1);
					CompoundStatementNode body = function.getBody();
					BlockItemNode lastStatement = body.getSequenceChild(body
							.numChildren() - 1);

					arguments.add(nodeFactory.newIdentifierExpressionNode(node
							.getSource(), nodeFactory.newIdentifierNode(
							node.getSource(), CIVL_FILESYSTEM)));
					functionCall = nodeFactory.newFunctionCallNode(node
							.getSource(), nodeFactory
							.newIdentifierExpressionNode(node.getSource(),
									nodeFactory.newIdentifierNode(
											node.getSource(),
											FILESYSTEM_DESTROY)), arguments,
							null);
					functionCallStatement = nodeFactory
							.newExpressionStatementNode(functionCall);
					if (!(lastStatement instanceof ReturnNode)) {
						body.addSequenceChild(functionCallStatement.copy());
					}
					this.addFreeBeforeReturn(body, functionCallStatement);
				}
			}
		}
	}

	/**
	 * Processes an AST node.
	 * 
	 * @param node
	 * @throws SyntaxException
	 */
	private void renameFunctionCalls(ASTNode node) throws SyntaxException {
		int numChildren = node.numChildren();

		for (int i = 0; i < numChildren; i++) {
			ASTNode child = node.child(i);

			if (child != null)
				this.renameFunctionCalls(node.child(i));
			// if (child.getSource().getFirstToken().getSourceFile().getName()
			// .equals("stdio-c.cvl")) {
			// if (child instanceof ExpressionStatementNode) {
			// ExpressionStatementNode expressionStatement =
			// (ExpressionStatementNode) child;
			//
			// if (expressionStatement.getExpression() instanceof
			// FunctionCallNode) {
			// FunctionCallNode functionCall = (FunctionCallNode)
			// expressionStatement
			// .getExpression();
			// String functionName = ((IdentifierExpressionNode) functionCall
			// .getFunction()).getIdentifier().name();
			//
			// if (functionName.equals("$filesystem_destroy")) {
			// this.freeCall = expressionStatement;
			// node.removeChild(i);
			// }
			// }
			// }
			// }
		}
		if (node instanceof FunctionCallNode) {
			this.processFunctionCall((FunctionCallNode) node);
		}
	}

	private void addFreeBeforeReturn(ASTNode node,
			ExpressionStatementNode functionCallStatement) {
		int numChildren = node.numChildren();

		for (int i = 0; i < numChildren; i++) {
			ASTNode child = node.child(i);

			if (child == null)
				continue;
			if (child instanceof ReturnNode) {
				List<BlockItemNode> statements = new ArrayList<>(2);

				statements.add(functionCallStatement.copy());
				statements.add((ReturnNode) child);
				node.removeChild(i);
				node.setChild(i, nodeFactory.newCompoundStatementNode(
						child.getSource(), statements));
			} else
				this.addFreeBeforeReturn(node.child(i), functionCallStatement);
		}
	}

	private void processFunctionCall(FunctionCallNode functionCall)
			throws SyntaxException {
		if (functionCall.getFunction().expressionKind() == ExpressionKind.IDENTIFIER_EXPRESSION) {
			IdentifierExpressionNode functionExpression = (IdentifierExpressionNode) functionCall
					.getFunction();
			String functionName = functionExpression.getIdentifier().name();
			String firstArgName = "";
			IdentifierNode functionNameIdentifer = functionExpression
					.getIdentifier();

			if (functionName.equals(PRINTF) || functionName.equals(SCANF)) {
				if (functionName.equals(PRINTF)) {
					functionNameIdentifer.setName(FPRINTF);
					firstArgName = STD_OUT;
				} else if (functionName.equals(SCANF)) {
					functionNameIdentifer.setName(FSCANF);
					firstArgName = STD_IN;
				}
				processPrintfOrScanf(functionCall, firstArgName);
			} else if (functionName.equals(FOPEN)) {
				functionNameIdentifer.setName(FOPEN_NEW);
				processFopen(functionCall);
			}
		}
	}

	/**
	 * fopen(filename, "mode") --> $fopen(CIVL_filesystem, filename,
	 * MODE_CONSTANT)
	 * 
	 * @param functionCall
	 * @throws SyntaxException
	 */
	private void processFopen(FunctionCallNode functionCall)
			throws SyntaxException {
		Source source = functionCall.getFunction().getSource();
		IdentifierExpressionNode civlFileSystem = nodeFactory
				.newIdentifierExpressionNode(source,
						nodeFactory.newIdentifierNode(source, CIVL_FILE_SYSTEM));
		int oldCount = functionCall.getNumberOfArguments();
		List<ExpressionNode> arguments = new ArrayList<>(oldCount + 1);
		ExpressionNode modeArg = functionCall.getArgument(1);

		arguments.add(civlFileSystem);
		arguments.add(functionCall.getArgument(0));
		if (modeArg instanceof StringLiteralNode) {
			StringValue value = ((StringLiteralNode) modeArg)
					.getConstantValue();
			String modeString = this.processFopenMode(value.toString(),
					modeArg.getSource());

			source = modeArg.getSource();
			arguments.add(nodeFactory.newIdentifierExpressionNode(source,
					nodeFactory.newIdentifierNode(source, modeString)));
		} else {
			throw new ABCUnsupportedException(
					"non-string-literal file mode of fopen");
		}
		for (int i = 0; i < oldCount; i++) {
			ExpressionNode argument = functionCall.getArgument(i);

			argument.parent().removeChild(argument.childIndex());
		}
		functionCall.setChild(1, nodeFactory.newSequenceNode(source,
				"ActualParameterList", arguments));
	}

	private String processFopenMode(String mode, Source source)
			throws SyntaxException {
		if (mode.equals(FOPEN_R))
			return CIVL_FILE_MODE_R;
		if (mode.equals(FOPEN_W))
			return CIVL_FILE_MODE_W;
		if (mode.equals(FOPEN_WX))
			return CIVL_FILE_MODE_WX;
		if (mode.equals(FOPEN_A))
			return CIVL_FILE_MODE_A;
		if (mode.equals(FOPEN_RB))
			return CIVL_FILE_MODE_RB;
		if (mode.equals(FOPEN_WB))
			return CIVL_FILE_MODE_WB;
		if (mode.equals(FOPEN_WBX))
			return CIVL_FILE_MODE_WBX;
		if (mode.equals(FOPEN_AB))
			return CIVL_FILE_MODE_AB;
		if (mode.equals(FOPEN_RP))
			return CIVL_FILE_MODE_RP;
		if (mode.equals(FOPEN_WP))
			return CIVL_FILE_MODE_WP;
		if (mode.equals(FOPEN_WPX))
			return CIVL_FILE_MODE_WPX;
		if (mode.equals(FOPEN_AP))
			return CIVL_FILE_MODE_AP;
		if (mode.equals(FOPEN_RPB) || mode.equals(FOPEN_RBP))
			return CIVL_FILE_MODE_RPB;
		if (mode.equals(FOPEN_WPB) || mode.equals(FOPEN_WBP))
			return CIVL_FILE_MODE_WPB;
		if (mode.equals(FOPEN_WPBX) || mode.equals(FOPEN_WBPX))
			return CIVL_FILE_MODE_WPBX;
		if (mode.equals(FOPEN_APB) || mode.equals(FOPEN_ABP))
			return CIVL_FILE_MODE_APB;
		throw new SyntaxException("Invalid mode " + mode + " of fopen.", source);
	}

	private void processPrintfOrScanf(FunctionCallNode functionCall,
			String firstArgName) {
		Source source = functionCall.getFunction().getSource();
		IdentifierExpressionNode firstArg = nodeFactory
				.newIdentifierExpressionNode(source,
						nodeFactory.newIdentifierNode(source, firstArgName));
		int oldCount = functionCall.getNumberOfArguments();
		List<ExpressionNode> arguments = new ArrayList<>(oldCount + 1);

		arguments.add(firstArg);
		for (int i = 0; i < oldCount; i++) {
			ExpressionNode argument = functionCall.getArgument(i);

			argument.parent().removeChild(argument.childIndex());
			arguments.add(argument);
		}
		functionCall.setChild(1, nodeFactory.newSequenceNode(source,
				"ActualParameterList", arguments));
	}

	// private void processOperator(OperatorNode expression)
	// throws SyntaxException {
	// int count = expression.getNumberOfArguments();
	//
	// for (int i = 0; i < count; i++) {
	// processExpression(expression.getArgument(i));
	// }
	// }

}