StatementAnalyzer.java

package dev.civl.abc.analysis.entity;

import java.util.Iterator;
import java.util.Stack;

import dev.civl.abc.ast.conversion.IF.ConversionFactory;
import dev.civl.abc.ast.entity.IF.Function;
import dev.civl.abc.ast.entity.IF.Label;
import dev.civl.abc.ast.node.IF.ASTNode;
import dev.civl.abc.ast.node.IF.ASTNode.NodeKind;
import dev.civl.abc.ast.node.IF.NodeFactory;
import dev.civl.abc.ast.node.IF.PragmaNode;
import dev.civl.abc.ast.node.IF.SequenceNode;
import dev.civl.abc.ast.node.IF.StaticAssertionNode;
import dev.civl.abc.ast.node.IF.acsl.ContractNode;
import dev.civl.abc.ast.node.IF.declaration.FunctionDeclarationNode;
import dev.civl.abc.ast.node.IF.declaration.TypedefDeclarationNode;
import dev.civl.abc.ast.node.IF.declaration.VariableDeclarationNode;
import dev.civl.abc.ast.node.IF.expression.ExpressionNode;
import dev.civl.abc.ast.node.IF.expression.FunctionCallNode;
import dev.civl.abc.ast.node.IF.expression.IdentifierExpressionNode;
import dev.civl.abc.ast.node.IF.label.LabelNode;
import dev.civl.abc.ast.node.IF.label.OrdinaryLabelNode;
import dev.civl.abc.ast.node.IF.label.SwitchLabelNode;
import dev.civl.abc.ast.node.IF.omp.OmpExecutableNode;
import dev.civl.abc.ast.node.IF.omp.OmpExecutableNode.OmpExecutableKind;
import dev.civl.abc.ast.node.IF.omp.OmpForNode;
import dev.civl.abc.ast.node.IF.omp.OmpFunctionReductionNode;
import dev.civl.abc.ast.node.IF.omp.OmpNode;
import dev.civl.abc.ast.node.IF.omp.OmpNode.OmpNodeKind;
import dev.civl.abc.ast.node.IF.omp.OmpParallelNode;
import dev.civl.abc.ast.node.IF.omp.OmpReductionNode;
import dev.civl.abc.ast.node.IF.omp.OmpReductionNode.OmpReductionNodeKind;
import dev.civl.abc.ast.node.IF.omp.OmpWorksharingNode;
import dev.civl.abc.ast.node.IF.statement.AtomicNode;
import dev.civl.abc.ast.node.IF.statement.BlockItemNode;
import dev.civl.abc.ast.node.IF.statement.ChooseStatementNode;
import dev.civl.abc.ast.node.IF.statement.CivlForNode;
import dev.civl.abc.ast.node.IF.statement.CompoundStatementNode;
import dev.civl.abc.ast.node.IF.statement.DeclarationListNode;
import dev.civl.abc.ast.node.IF.statement.ExpressionStatementNode;
import dev.civl.abc.ast.node.IF.statement.ForLoopInitializerNode;
import dev.civl.abc.ast.node.IF.statement.ForLoopNode;
import dev.civl.abc.ast.node.IF.statement.IfNode;
import dev.civl.abc.ast.node.IF.statement.JumpNode;
import dev.civl.abc.ast.node.IF.statement.JumpNode.JumpKind;
import dev.civl.abc.ast.node.IF.statement.LabeledStatementNode;
import dev.civl.abc.ast.node.IF.statement.LoopNode;
import dev.civl.abc.ast.node.IF.statement.ReturnNode;
import dev.civl.abc.ast.node.IF.statement.RunNode;
import dev.civl.abc.ast.node.IF.statement.StatementNode;
import dev.civl.abc.ast.node.IF.statement.StatementNode.StatementKind;
import dev.civl.abc.ast.node.IF.statement.SwitchNode;
import dev.civl.abc.ast.node.IF.statement.UpdateNode;
import dev.civl.abc.ast.node.IF.statement.WhenNode;
import dev.civl.abc.ast.node.IF.type.EnumerationTypeNode;
import dev.civl.abc.ast.node.IF.type.StructureOrUnionTypeNode;
import dev.civl.abc.ast.type.IF.DomainType;
import dev.civl.abc.ast.type.IF.IntegerType;
import dev.civl.abc.ast.type.IF.ObjectType;
import dev.civl.abc.ast.type.IF.Type;
import dev.civl.abc.ast.type.IF.Type.TypeKind;
import dev.civl.abc.ast.type.IF.TypeFactory;
import dev.civl.abc.ast.value.IF.Value;
import dev.civl.abc.config.IF.Configuration;
import dev.civl.abc.err.IF.ABCRuntimeException;
import dev.civl.abc.token.IF.SyntaxException;
import dev.civl.abc.token.IF.UnsourcedException;

//import dev.civl.abc.ast.node.IF.statement.AssertNode;

public class StatementAnalyzer {

	// ***************************** Fields *******************************

	private EntityAnalyzer entityAnalyzer;

	private NodeFactory nodeFactory;

	private ExpressionAnalyzer expressionAnalyzer;

	private TypeFactory typeFactory;

	private ConversionFactory conversionFactory;

	private AcslContractAnalyzer acslAnalyzer;

	// ************************** Constructors ****************************

	StatementAnalyzer(EntityAnalyzer entityAnalyzer, ExpressionAnalyzer expressionAnalyzer,
			ConversionFactory conversionFactory, TypeFactory typeFactory, Configuration config) {
		this.entityAnalyzer = entityAnalyzer;
		this.nodeFactory = entityAnalyzer.nodeFactory;
		this.expressionAnalyzer = expressionAnalyzer;
		this.conversionFactory = conversionFactory;
		this.typeFactory = typeFactory;
		this.acslAnalyzer = new AcslContractAnalyzer(entityAnalyzer, conversionFactory);
	}

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

	private SyntaxException error(String message, ASTNode node) {
		return entityAnalyzer.error(message, node);
	}

	private SyntaxException error(UnsourcedException e, ASTNode node) {
		return entityAnalyzer.error(e, node);
	}

	private void processExpression(ExpressionNode expression) throws SyntaxException {
		if (expression != null)
			expressionAnalyzer.processExpression(expression);
	}

	private void processIf(IfNode node) throws SyntaxException {
		processExpression(node.getCondition());
		processStatement(node.getTrueBranch());
		if (node.getFalseBranch() != null)
			processStatement(node.getFalseBranch());
	}

	private SwitchNode enclosingSwitch(SwitchLabelNode labelNode) {
		for (ASTNode node = labelNode.parent(); node != null; node = node.parent()) {
			if (node instanceof SwitchNode)
				return (SwitchNode) node;
		}
		return null;
	}

	private ASTNode enclosingSwitchOrChoose(SwitchLabelNode labelNode) {
		for (ASTNode node = labelNode.parent(); node != null; node = node.parent()) {
			if (node instanceof SwitchNode || node instanceof ChooseStatementNode)
				return node;
		}
		return null;
	}

	private void processLabeledStatement(LabeledStatementNode node) throws SyntaxException {
		LabelNode labelNode = node.getLabel();
		StatementNode statementNode = node.getStatement();
		Function function = entityAnalyzer.enclosingFunction(node);

		if (function == null)
			throw error("Label occurs outside of function", node);
		labelNode.setStatement(statementNode);
		if (labelNode instanceof OrdinaryLabelNode)
			processOrdinaryLabel((OrdinaryLabelNode) labelNode, function);
		else if (labelNode instanceof SwitchLabelNode)
			processSwitchLabel(node, (SwitchLabelNode) labelNode, function);
		else
			throw new RuntimeException("unreachable");
		processStatement(statementNode);

	}

	private void processOrdinaryLabel(OrdinaryLabelNode node, Function function) throws SyntaxException {
		Label label = entityAnalyzer.entityFactory.newLabel(node);

		node.setFunction(function);
		node.setEntity(label);
		node.getIdentifier().setEntity(label);
		try {
			function.getScope().add(label);
		} catch (UnsourcedException e) {
			throw error(e, node);
		}
	}

	private void processSwitchLabel(LabeledStatementNode labeledStatement, SwitchLabelNode switchLabel,
			Function function) throws SyntaxException {

		if (switchLabel.isDefault()) {
			ASTNode enclosing = enclosingSwitchOrChoose(switchLabel);

			if (enclosing instanceof ChooseStatementNode) {
				ChooseStatementNode choose = (ChooseStatementNode) enclosing;
				LabeledStatementNode oldDefault = choose.getDefaultCase();

				if (oldDefault != null)
					throw error("Two default cases in choose statement.  First was at " + oldDefault.getSource(),
							switchLabel);
				choose.setDefaultCase(labeledStatement);
				return;
			}
		}

		SwitchNode switchNode = enclosingSwitch(switchLabel);

		if (switchNode == null)
			throw error("Switch label occurs outside of any switch statement", switchLabel);
		if (switchLabel.isDefault()) {
			LabeledStatementNode oldDefault = switchNode.getDefaultCase();

			if (oldDefault != null)
				throw error("Two default cases in switch statement.  First was at " + oldDefault.getSource(),
						switchLabel);
			switchNode.setDefaultCase(labeledStatement);
		} else {
			ExpressionNode caseExpression = switchLabel.getExpression();
			Iterator<LabeledStatementNode> cases = switchNode.getCases();
			Value constant;

			expressionAnalyzer.processExpression(caseExpression);
			if (!caseExpression.isConstantExpression()) {
				throw error("Case expression not constant", caseExpression);
			}
			constant = nodeFactory.getConstantValue(caseExpression);
			while (cases.hasNext()) {
				SwitchLabelNode labelNode = (SwitchLabelNode) cases.next().getLabel();
				ExpressionNode oldExpression = labelNode.getExpression();
				Value oldConstant = nodeFactory.getConstantValue(oldExpression);

				if (constant.equals(oldConstant))
					throw error("Case constant appears twice: first time was at " + oldExpression, caseExpression);
			}
			switchNode.addCase(labeledStatement);
		}
	}

	private void processJump(JumpNode statement) throws SyntaxException {
		switch (statement.getKind()) {
		case RETURN: {
			ExpressionNode expression = ((ReturnNode) statement).getExpression();
			Function function = entityAnalyzer.enclosingFunction(statement);
			ObjectType returnType = function.getType().getReturnType();
			boolean returnTypeIsVoid = returnType.kind() == TypeKind.VOID;

			if (expression == null) {
				if (!returnTypeIsVoid)
					throw error("Missing expression in return statement", statement);
			} else {
				if (returnTypeIsVoid)
					throw error("Argument for return in function returning void", statement);
				if (expression != null)
					processExpression(expression);
				try {
					expressionAnalyzer.processAssignment(returnType, expression);
				} catch (UnsourcedException e) {
					throw error(e, expression);
				}
			}
		}
		case GOTO: // taken care of later in processGotos
		case BREAK: // nothing to do
		case CONTINUE: // nothing to do
			break;
		default:
			throw new RuntimeException("Unreachable");
		}
	}

	private void processLoop(LoopNode loopNode) throws SyntaxException {
		SequenceNode<ContractNode> loopContracts = loopNode.loopContracts();

		switch (loopNode.getKind()) {
		case WHILE:
			processExpression(loopNode.getCondition());
			processStatement(loopNode.getBody());
			// processExpression(loopNode.getInvariant());
			break;
		case DO_WHILE:
			processStatement(loopNode.getBody());
			processExpression(loopNode.getCondition());
			// processExpression(loopNode.getInvariant());
			break;
		case FOR: {
			ForLoopNode forNode = (ForLoopNode) loopNode;
			ForLoopInitializerNode initializer = forNode.getInitializer();

			if (initializer == null) {
			} else if (initializer instanceof ExpressionNode) {
				processExpression((ExpressionNode) initializer);
			} else if (initializer instanceof DeclarationListNode) {
				DeclarationListNode declarationList = (DeclarationListNode) initializer;

				for (VariableDeclarationNode child : declarationList) {
					if (child == null)
						continue;
					entityAnalyzer.declarationAnalyzer.processVariableDeclaration(child);
				}
			} else
				throw error("Unknown kind of initializer clause in for loop", initializer);
			processExpression(loopNode.getCondition());
			processExpression(forNode.getIncrementer());
			processStatement(loopNode.getBody());
			// processExpression(loopNode.getInvariant());
			break;
		}
		default:
			throw new RuntimeException("Unreachable");
		}
		if (loopContracts != null) {
			acslAnalyzer.processLoopContractNodes(loopContracts);
		}
	}

	private void processCivlFor(CivlForNode node) throws SyntaxException {
		ExpressionNode domainNode = node.getDomain();
		int numVars = 0;
		Type domainNodeType;
		DomainType domainType;
		int domainDimension;

		for (VariableDeclarationNode child : node.getVariables()) {
			Type type;

			entityAnalyzer.declarationAnalyzer.processVariableDeclaration(child);
			if (child.getInitializer() != null)
				throw error("Loop variable " + numVars + " in $for/$parfor statement has initializer", child);
			type = child.getTypeNode().getType();
			if (!(type instanceof IntegerType))
				throw error("Loop variable " + numVars + " in $for/$parfor has non-integer type: " + type,
						child.getTypeNode());
			numVars++;
		}
		expressionAnalyzer.processExpression(domainNode);
		domainNodeType = domainNode.getConvertedType();
		if (domainNodeType.equals(typeFactory.rangeType())) {
			domainNode.addConversion(conversionFactory.regularRangeToDomainConversion((ObjectType) domainNodeType,
					typeFactory.domainType(1)));
			domainNodeType = domainNode.getConvertedType();
		} else if (!(domainNodeType instanceof DomainType))
			throw error("Domain expression in $for/$parfor does not have $domain type", domainNode);
		domainType = (DomainType) domainNodeType;
		if (!domainType.hasDimension())
			throw error("Use of incomplete domain type in $for/$parfor", domainNode);
		domainDimension = domainType.getDimension();
		if (domainDimension != numVars)
			throw error("Dimension of domain (" + domainDimension + ") " + "does not equal number of loop variables ("
					+ numVars + ")", domainNode);
		processStatement(node.getBody());
		if (node.loopContracts() != null)
			acslAnalyzer.processLoopContractNodes(node.loopContracts());
	}

	// ************************* Exported Methods **************************

	void processStatement(StatementNode statement) throws SyntaxException {
		StatementKind kind = statement.statementKind();

		switch (kind) {
		case COMPOUND:
			processCompoundStatement((CompoundStatementNode) statement);
			break;
		case EXPRESSION:
			processExpression(((ExpressionStatementNode) statement).getExpression());
			break;
		case IF:
			processIf((IfNode) statement);
			break;
		case JUMP:
			processJump((JumpNode) statement);
			break;
		case LABELED:
			processLabeledStatement((LabeledStatementNode) statement);
			break;
		case LOOP:
			processLoop((LoopNode) statement);
			break;
		case SWITCH:
			processExpression(((SwitchNode) statement).getCondition());
			processStatement(((SwitchNode) statement).getBody());
			break;
		case PRAGMA:
			entityAnalyzer.processPragma((PragmaNode) statement);
			break;
		case RUN:
			processRunStatement((RunNode) statement);
			break;
		case OMP:
			processOmpNode((OmpNode) statement);
			break;
		case NULL:
			break;
		case WHEN: {
			ExpressionNode guard = ((WhenNode) statement).getGuard();
			Type guardType;

			processExpression(guard);
			if (!guard.isSideEffectFree(false))
				throw this.error("the guard of a $when statement is not allowed to have side effects.", guard);
			guardType = guard.getConvertedType();
			// check guardType can be converted to a boolean...
			if (!guardType.isScalar())
				throw error("Guard has non-scalar type " + guardType, guard);
			processStatement(((WhenNode) statement).getBody());
			break;
		}
		case UPDATE: {
			UpdateNode updateNode = (UpdateNode) statement;

			processExpression(updateNode.getCollator());
			processExpression(updateNode.getFunctionCall());
			break;
		}
		case CHOOSE: {
			ChooseStatementNode chooseStatement = (ChooseStatementNode) statement;

			for (StatementNode child : chooseStatement)
				processStatement(child);
			break;
		}
		case ATOMIC:
			processStatement(((AtomicNode) statement).getBody());
			break;
		case CIVL_FOR:
			processCivlFor((CivlForNode) statement);
			break;
		default:
			throw error("Unknown kind of statement", statement);
		}
	}

	private void processOmpNode(OmpNode ompNode) throws SyntaxException {
		OmpNodeKind ompKind = ompNode.ompNodeKind();

		switch (ompKind) {
		case EXECUTABLE:
			processOmpExecutableNode((OmpExecutableNode) ompNode);
			break;
		case DECLARATIVE:
		default:

		}
	}

	/**
	 * Process the body statements of a $run statement. Apply checking for no return
	 * node is allowed inside the body of the $run statement.
	 * 
	 * @param runNode The {@link RunNode}
	 * @throws SyntaxException
	 */
	private void processRunStatement(RunNode runNode) throws SyntaxException {
		processStatement(runNode.getStatement());

		StatementNode body = runNode.getStatement();
		ASTNode next = body;
		Stack<ASTNode> children = new Stack<ASTNode>();

		children.push(next);
		while (!children.isEmpty()) {
			ASTNode item = children.pop();

			if (item != null) {
				if (item.nodeKind() == NodeKind.STATEMENT)
					if (((StatementNode) item).statementKind() == StatementKind.JUMP)
						if (((JumpNode) item).getKind() == JumpKind.RETURN)
							throw error("No return statement is allowed to be inside a $run statement block.", item);
				for (ASTNode child : item.children())
					children.push(child);
			}
		}
	}

	private void processOmpExecutableNode(OmpExecutableNode statement) throws SyntaxException {
		OmpExecutableKind kind = statement.ompExecutableKind();
		SequenceNode<OmpReductionNode> reductionList = (SequenceNode<OmpReductionNode>) statement.reductionList();

		for (int i = 0; i <= 5; i++) {
			@SuppressWarnings("unchecked")
			SequenceNode<ExpressionNode> list = (SequenceNode<ExpressionNode>) statement.child(i);

			if (list != null) {
				int count = list.numChildren();

				for (int j = 0; j < count; j++) {
					this.expressionAnalyzer.processExpression(list.getSequenceChild(j));
				}
			}
		}
		if (reductionList != null) {
			int count = reductionList.numChildren();

			for (int j = 0; j < count; j++) {
				this.processOmpReductionNode(reductionList.getSequenceChild(j));
			}
		}
		switch (kind) {
		case PARALLEL:
			OmpParallelNode parallel = (OmpParallelNode) statement;

			if (parallel.ifClause() != null)
				processExpression(parallel.ifClause());
			if (parallel.numThreads() != null)
				processExpression(parallel.numThreads());
			break;
		case WORKSHARING:
			OmpWorksharingNode workshare = (OmpWorksharingNode) statement;

			switch (workshare.ompWorkshareNodeKind()) {
			case FOR:
				OmpForNode forNode = (OmpForNode) statement;
				SequenceNode<FunctionCallNode> assertions = forNode.assertions();
				FunctionCallNode invariant = forNode.invariant();
				ExpressionNode chunkSize = forNode.chunkSize();

				if (assertions != null) {
					for (FunctionCallNode node : assertions)
						processExpression(node);
				}
				if (invariant != null)
					processExpression(invariant);
				if (chunkSize != null)
					processExpression(chunkSize);
				break;
			default:
			}
			break;
		case SIMD:
			// TODO: there are more of SIMD clauses under construction
		case SYNCHRONIZATION:
			break;
		default:
			throw new ABCRuntimeException("unknown OpenMP executable node kind to StatementAnalyzer: " + kind
					+ "\nStatement: " + statement.prettyRepresentation());
		}
		if (statement.statementNode() != null)
			processStatement(statement.statementNode());
	}

	private void processOmpReductionNode(OmpReductionNode reduction) throws SyntaxException {
		OmpReductionNodeKind kind = reduction.ompReductionOperatorNodeKind();
		SequenceNode<IdentifierExpressionNode> list = reduction.variables();
		int count = list.numChildren();

		if (kind == OmpReductionNodeKind.FUNCTION) {
			this.expressionAnalyzer.processExpression(((OmpFunctionReductionNode) reduction).function());
		}
		for (int i = 0; i < count; i++) {
			this.expressionAnalyzer.processExpression(list.getSequenceChild(i));
		}
	}

	/**
	 * <ul>
	 * <li>StatementNode</li> (includes PragmaNode)
	 * <li>StructureOrUnionTypeNode</li>
	 * <li>EnumerationTypeNode</li>
	 * <li>StaticAssertionNode</li>
	 * <li>VariableDeclarationNode</li>
	 * <li>FunctionDeclarationNode</li> (but not a FunctionDefinitionNode)
	 * <li>TypedefDeclarationNode</li>
	 * </ul>
	 * 
	 * @param node
	 * @throws SyntaxException
	 */
	void processCompoundStatement(CompoundStatementNode node) throws SyntaxException {
		for (BlockItemNode item : node) {
			if (item == null)
				continue;
			if (item instanceof StatementNode)
				processStatement((StatementNode) item);
			else if (item instanceof StructureOrUnionTypeNode)
				entityAnalyzer.typeAnalyzer.processStructureOrUnionType((StructureOrUnionTypeNode) item);
			else if (item instanceof EnumerationTypeNode)
				entityAnalyzer.typeAnalyzer.processEnumerationType((EnumerationTypeNode) item);
			else if (item instanceof StaticAssertionNode)
				entityAnalyzer.processStaticAssertion((StaticAssertionNode) item);
			else if (item instanceof VariableDeclarationNode)
				entityAnalyzer.declarationAnalyzer.processVariableDeclaration((VariableDeclarationNode) item);
			else if (item instanceof FunctionDeclarationNode)
				entityAnalyzer.declarationAnalyzer.processFunctionDeclaration((FunctionDeclarationNode) item);
			else if (item instanceof TypedefDeclarationNode)
				entityAnalyzer.declarationAnalyzer.processTypedefDeclaration((TypedefDeclarationNode) item);
			else
				throw error("Unknown kind of block item", item);
		}
	}
}