CommonScope.java

/**
 * 
 */
package dev.civl.mc.model.common;

import java.io.PrintStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dev.civl.mc.model.IF.CIVLFunction;
import dev.civl.mc.model.IF.CIVLSource;
import dev.civl.mc.model.IF.Identifier;
import dev.civl.mc.model.IF.Model;
import dev.civl.mc.model.IF.Scope;
import dev.civl.mc.model.IF.type.CIVLArrayType;
import dev.civl.mc.model.IF.type.CIVLBundleType;
import dev.civl.mc.model.IF.type.CIVLHeapType;
import dev.civl.mc.model.IF.type.CIVLPointerType;
import dev.civl.mc.model.IF.type.CIVLPrimitiveType;
import dev.civl.mc.model.IF.type.CIVLStructOrUnionType;
import dev.civl.mc.model.IF.type.CIVLType;
import dev.civl.mc.model.IF.type.CIVLType.TypeKind;
import dev.civl.mc.model.IF.type.StructOrUnionField;
import dev.civl.mc.model.IF.variable.Variable;

/**
 * A scope.
 * 
 * @author Timothy K. Zirkel (zirkel)
 * 
 */
public class CommonScope extends CommonSourceable implements Scope {

	/**
	 * The parent scope of this scope. Each scope has exactly one parent scope,
	 * except for the root scope which has no parent.
	 */
	private Scope parent;
	private Variable[] variables;
	private String[] functionNames;
	private Map<String, CIVLFunction> functions;
	private Set<Scope> children = new LinkedHashSet<Scope>();
	private Collection<Variable> procRefs = new HashSet<Variable>();
	private Collection<Variable> stateRefs = new HashSet<Variable>();
	private Collection<Variable> scopeRefs = new HashSet<Variable>();
	private Collection<Variable> pointerRefs = new HashSet<Variable>();
	private Collection<Variable> varsNeedSymbolicConstant = new LinkedHashSet<Variable>();
	private int id;
	private CIVLFunction function;

	/**
	 * A scope.
	 * 
	 * @param parent
	 *            The containing scope of this scope. Only null for the
	 *            outermost scope of the designated "System" function.
	 * 
	 * @param variables
	 *            The set of variables in this scope.
	 */
	public CommonScope(CIVLSource source, Scope parent, Set<Variable> variables,
			int id) {
		super(source);
		this.parent = parent;
		this.variables = new Variable[variables.size()];
		this.functions = new HashMap<>();
		this.functionNames = new String[0];
		for (Variable v : variables) {
			assert this.variables[v.vid()] == null;
			this.variables[v.vid()] = v;
			v.setScope(this);
			// checkProcRef(v);
			// checkPointer(v);
			// checkScopeRef(v);
		}
		this.id = id;
	}

	/**
	 * @return The containing scope of this scope.
	 */
	public Scope parent() {
		return parent;
	}

	@Override
	public Variable[] variables() {
		return variables;
	}

	/**
	 * @return The number of variables contained in this scope.
	 */
	public int numVariables() {
		return variables.length;
	}

	/**
	 * @return The id of this scope.
	 */
	public int id() {
		return id;
	}

	/**
	 * @return The scopes contained by this scope.
	 */
	public Set<Scope> children() {
		return children;
	}

	/**
	 * @param parent
	 *            The containing scope of this scope.
	 */
	public void setParent(Scope parent) {
		this.parent = parent;
	}

	/**
	 * @param variables
	 *            The set of variables contained in this scope.
	 */
	public void setVariables(Set<Variable> variables) {
		this.variables = new Variable[variables.size()];
		for (Variable v : variables) {
			assert this.variables[v.vid()] == null;
			this.variables[v.vid()] = v;
			// checkProcRef(v);
			// checkPointer(v);
			// checkScopeRef(v);
		}
	}

	/**
	 * @param children
	 *            The scopes contained by this scope.
	 */
	public void setChildren(Set<Scope> children) {
		this.children = children;
	}

	/**
	 * @param A
	 *            new scope contained by this scope.
	 */
	public void addChild(Scope child) {
		children.add(child);
	}

	@Override
	public void addFunction(CIVLFunction function) {
		String[] oldFunctionNames = this.functionNames;

		this.functions.put(function.name().name(), function);
		this.functionNames = new String[oldFunctionNames.length + 1];
		for (int i = 0; i < oldFunctionNames.length; i++)
			functionNames[i] = oldFunctionNames[i];
		assert function.fid() == oldFunctionNames.length;
		functionNames[oldFunctionNames.length] = function.name().name();
	}

	/**
	 * A new variable in this scope.
	 */
	public void addVariable(Variable variable) {
		Variable[] oldVariables = variables;

		variables = new Variable[oldVariables.length + 1];
		for (int i = 0; i < oldVariables.length; i++) {
			variables[i] = oldVariables[i];
		}
		assert variable.vid() == oldVariables.length;
		variables[oldVariables.length] = variable;
		// checkProcRef(variable);
		// checkScopeRef(variable);
		// checkPointer(variable);
		variable.setScope(this);
	}

	/**
	 * Get the variable associated with an identifier. If this scope does not
	 * contain such a variable, parent scopes will be recursively checked.
	 * 
	 * @param name
	 *            The identifier for the variable.
	 * @return The model representation of the variable in this scope hierarchy,
	 *         or null if not found.
	 */
	public Variable variable(Identifier name) {
		for (Variable v : variables) {
			if (v.name().equals(name)) {
				return v;
			}
		}
		if (parent != null) {
			return parent.variable(name);
		}
		return null;
	}

	@Override
	public boolean containsVariable(String name) {
		for (Variable v : variables) {
			if (v.name().name().equals(name)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Get the variable at the specified array index.
	 * 
	 * @param vid
	 *            The index of the variable. Should be in the range
	 *            [0,numVariable()-1].
	 * @return The variable at the index.
	 */
	public Variable variable(int vid) {
		return variables[vid];
	}

	/**
	 * @param function
	 *            The function containing this scope.
	 */
	public void setFunction(CIVLFunction function) {
		this.function = function;
	}

	/**
	 * @return The function containing this scope.
	 */
	public CIVLFunction function() {
		return function;
	}

	/**
	 * @return The identifier of the function containing this scope.
	 */
	public Identifier functionName() {
		return function.name();
	}

	/**
	 * A variables has a "procRefType" if it is of type Process, if it is an
	 * array with element of procRefType, or if it is a struct with fields of
	 * procRefType.
	 * 
	 * @return A collection of the variables in this scope with a procRefType.
	 */
	public Collection<Variable> variablesWithProcrefs() {
		return procRefs;
	}

	@Override
	public Collection<Variable> variablesWithStaterefs() {
		return stateRefs;
	}

	/**
	 * A variables has a "scopeRefType" if it is of type Scope, if it is an
	 * array with element of scopeRefType, if it is a struct with fields of
	 * scopeRefType, or if it contains a pointer.
	 * 
	 * @return A collection of the variables in this scope with a scopeRefType.
	 */
	public Collection<Variable> variablesWithScoperefs() {
		return scopeRefs;
	}

	/**
	 * A variable contains a pointer type if it is of type PointerType, if it is
	 * an array with elements containing pointer type, or if it is a struct with
	 * fields containing pointer type.
	 * 
	 * @return A collection of the variables in this scope containing pointer
	 *         types.
	 */
	public Collection<Variable> variablesWithPointers() {
		return pointerRefs;
	}

	/**
	 * Checks if a variables is a procRefType. If it is, it gets added to
	 * procRefs.
	 * 
	 * @param v
	 *            The variable being checked.
	 */
	private void checkProcRef(Variable variable) {
		boolean procRefType = containsProcType(variable.type());

		if (procRefType) {
			procRefs.add(variable);
		}
	}

	/**
	 * Checks if a variables contains a $state Type. If it is, it gets added to
	 * stateRefs.
	 * 
	 * @param v
	 *            The variable being checked.
	 */
	private void checkStateRef(Variable variable) {
		if (containsStateType(variable.type())) {
			stateRefs.add(variable);
			scopeRefs.add(variable);
		}
	}

	/**
	 * Checks if a variables is a scopeRefType. If it is, it gets added to
	 * scopeRefs.
	 * 
	 * @param v
	 *            The variable being checked.
	 */
	private void checkScopeRef(Variable variable) {
		CIVLType type = variable.type();
		boolean scopeRefType = containsScopeType(type);

		if (scopeRefType) {
			scopeRefs.add(variable);
		} else if (containsPointerType(type)) {
			scopeRefs.add(variable);
		}
	}

	/**
	 * Checks if a variables is a pointer. If it is, it gets added to pointers.
	 * 
	 * @param v
	 *            The variable being checked.
	 */
	private void checkPointer(Variable variable) {
		boolean pointerType = containsPointerType(variable.type());

		if (pointerType) {
			pointerRefs.add(variable);
			variable.setPointerRef(true);
		}
	}

	/**
	 * Checks if a variables is of primitive type. If it is NOT, it gets added
	 * to non-primitive variables.
	 * 
	 * @param variable
	 *            The variable being checked.
	 */
	private void needsSymbolicConstant(Variable variable) {
		boolean needsSymbolicConstant = this
				.needsSymbolicConstantWorker(variable);

		if (needsSymbolicConstant)
			this.varsNeedSymbolicConstant.add(variable);
	}

	private boolean needsSymbolicConstantWorker(Variable variable) {
		if (variable.vid() == 0 || variable.isInput() || variable.isBound())
			return true;
		else {
			CIVLType type = variable.type();

			if (type instanceof CIVLPrimitiveType || type.isPointerType()
					|| type.isDomainType())
				return false;
		}
		return true;
	}

	private boolean containsPointerType(CIVLType type) {
		boolean containsPointerType = false;

		if (type instanceof CIVLPointerType) {
			containsPointerType = true;
		} else if (type instanceof CIVLArrayType) {
			containsPointerType = containsPointerType(
					((CIVLArrayType) type).elementType());
		} else if (type instanceof CIVLStructOrUnionType) {
			for (StructOrUnionField f : ((CIVLStructOrUnionType) type)
					.fields()) {
				boolean fieldContainsPointer = containsPointerType(f.type());

				containsPointerType = containsPointerType
						|| fieldContainsPointer;
			}
		} else if (type.isHeapType()) {
			for (int i = 0; i < ((CIVLHeapType) type).getNumMallocs(); i++) {
				boolean elementContainsPointer = containsPointerType(
						((CIVLHeapType) type).getMalloc(i)
								.getStaticElementType());

				containsPointerType = containsPointerType
						|| elementContainsPointer;
			}
		} else if (type.isBundleType()) {
			List<CIVLType> types = ((CIVLBundleType) type).types();

			for (CIVLType elementType : types) {
				boolean elementContainsPointer = containsPointerType(
						elementType);

				containsPointerType = containsPointerType
						|| elementContainsPointer;
			}
		} else if (type.typeKind() == TypeKind.MEM)
			// MEM type is a set of pointers hence contains scope refs:
			containsPointerType = true;
		return containsPointerType;
	}

	private boolean containsScopeType(CIVLType type) {
		boolean containsScopeType = false;

		if (type.isScopeType() || type.isFunction() || type.isPointerType()) {
			containsScopeType = true;
		} else if (type.isArrayType()) {
			containsScopeType = containsScopeType(
					((CIVLArrayType) type).elementType());
		} else if (type.isStructType()) {
			for (StructOrUnionField f : ((CIVLStructOrUnionType) type)
					.fields()) {
				boolean fieldContainsScope = containsScopeType(f.type());

				containsScopeType = containsScopeType || fieldContainsScope;
			}
		} else if (type.isHeapType()) {
			for (int i = 0; i < ((CIVLHeapType) type).getNumMallocs(); i++) {
				boolean elementContainsScope = containsScopeType(
						((CIVLHeapType) type).getMalloc(i)
								.getStaticElementType());

				containsScopeType = containsScopeType || elementContainsScope;
			}
		} else if (type.isBundleType()) {
			List<CIVLType> types = ((CIVLBundleType) type).types();

			for (CIVLType elementType : types) {
				boolean elementContainsScope = containsScopeType(elementType);

				containsScopeType = containsScopeType || elementContainsScope;
			}
		}
		return containsScopeType;
	}

	private boolean containsProcType(CIVLType type) {
		boolean containsProcType = false;

		if (type.isProcessType()) {
			containsProcType = true;
		} else if (type.isArrayType()) {
			containsProcType = containsProcType(
					((CIVLArrayType) type).elementType());
		} else if (type.isStructType()) {
			for (StructOrUnionField f : ((CIVLStructOrUnionType) type)
					.fields()) {
				boolean fieldContainsProc = containsProcType(f.type());

				containsProcType = containsProcType || fieldContainsProc;
			}
		} else if (type.isHeapType()) {
			for (int i = 0; i < ((CIVLHeapType) type).getNumMallocs(); i++) {
				boolean elementContainsProc = containsProcType(
						((CIVLHeapType) type).getMalloc(i)
								.getStaticElementType());

				containsProcType = containsProcType || elementContainsProc;
			}
		} else if (type.isBundleType()) {
			List<CIVLType> types = ((CIVLBundleType) type).types();

			for (CIVLType elementType : types) {
				boolean elementContainsProc = containsProcType(elementType);

				containsProcType = containsProcType || elementContainsProc;
			}
		}
		return containsProcType;
	}

	private boolean containsStateType(CIVLType type) {
		if (type.isStateType()) {
			return true;
		} else if (type.isArrayType()) {
			return containsStateType(((CIVLArrayType) type).elementType());
		} else if (type.isStructType()) {
			for (StructOrUnionField f : ((CIVLStructOrUnionType) type)
					.fields()) {
				boolean fieldContainsState = containsStateType(f.type());

				if (fieldContainsState)
					return true;
			}
		} else if (type.isHeapType()) {
			for (int i = 0; i < ((CIVLHeapType) type).getNumMallocs(); i++) {
				boolean elementContainsState = containsStateType(
						((CIVLHeapType) type).getMalloc(i)
								.getStaticElementType());

				if (elementContainsState)
					return true;
			}
		} else if (type.isBundleType()) {
			List<CIVLType> types = ((CIVLBundleType) type).types();

			for (CIVLType elementType : types) {
				boolean elementContainsState = containsStateType(elementType);

				if (elementContainsState)
					return true;
			}
		}
		return false;
	}

	@Override
	public String toString() {
		String result = "Variables : {";

		for (int i = 0; i < variables.length; i++) {
			if (i != 0) {
				result += ", ";
			}
			result += variables[i].toString();
		}
		result += "}";
		return result;
	}

	@Override
	public void print(String prefix, PrintStream out, boolean isDebug) {
		String parentID = "";

		if (parent == null) {
			parentID += "null";
		} else {
			parentID += parent.id();
		}
		out.println(prefix + "scope " + id + " (parent: " + parentID + ")");
		for (Variable v : variables) {
			if (isDebug) {
				out.println(prefix + "| " + v + " (purely local)");
			} else {
				out.println(prefix + "| " + v);
			}
		}
		for (Scope child : children) {
			if (child.function().equals(function)) {
				child.print(prefix + "| ", out, isDebug);
			}
		}
		out.flush();
	}

	@Override
	public CIVLFunction getFunction(Identifier name) {
		String functionName = name.name();

		if (this.functions.containsKey(functionName))
			return functions.get(functionName);
		if (this.parent != null)
			return this.parent.getFunction(name);
		return null;
	}

	@Override
	public CIVLFunction getFunction(String name) {
		if (this.functions.containsKey(name))
			return functions.get(name);
		if (this.parent != null)
			return this.parent.getFunction(name);
		return null;
	}

	public int getVid(Variable staticVariable) {
		int result = -1;

		for (int i = 0; i < variables.length; i++) {
			if (variables[i].equals(staticVariable)) {
				result = i;
				break;
			}
		}
		// TODO If not found, either throw error here or catch higher up
		return result;
	}

	@Override
	public Model model() {
		return function.model();
	}

	@Override
	public boolean isDescendantOf(Scope anc) {
		Scope parent = this.parent;

		while (parent != null) {
			if (parent.id() == anc.id())
				return true;
			parent = parent.parent();
		}
		return false;
	}

	@Override
	public Variable variable(String name) {
		for (Variable v : variables) {
			if (v.name().name().equals(name)) {
				return v;
			}
		}
		return null;
	}

	@Override
	public void complete() {
		for (Variable v : variables) {
			this.checkPointer(v);
			this.checkScopeRef(v);
			this.checkProcRef(v);
			this.checkStateRef(v);
			this.needsSymbolicConstant(v);
		}
		if (children != null)
			for (Scope child : children)
				child.complete();
	}

	@Override
	public Collection<Variable> varsNeedSymbolicConstant() {
		return this.varsNeedSymbolicConstant;
	}

	@Override
	public boolean hasVariable() {
		return variables != null && variables.length > 0;
	}

	@Override
	public boolean hasVariableWtPointer() {
		return this.pointerRefs != null && this.pointerRefs.size() > 0;
	}

	@Override
	public int numFunctions() {
		return this.functionNames.length;
	}

	@Override
	public CIVLFunction getFunction(int fid) {
		return this.functions.get(functionNames[fid]);
	}

	@Override
	public Variable contains(Variable variable) {
		for (Variable v : variables) {
			if (v.name().name().equals(variable.name().name())) {
				return v;
			}
		}
		return null;
	}
}