package java_cup;

import java.util.BitSet;

/** A set of terminals implemented as a bitset. 
 * @version last updated: 11/25/95
 * @author  Scott Hudson
 */
public class terminal_set {

	/*-----------------------------------------------------------*/
	/*--- Constructor(s) ----------------------------------------*/
	/*-----------------------------------------------------------*/

	/** Constructor for an empty set. */
	public terminal_set()
	{
		/* allocate the bitset at what is probably the right size */
		_elements = new BitSet(terminal.number());
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Constructor for cloning from another set. 
	 * @param other the set we are cloning from.
	 */
	public terminal_set(terminal_set other)
			throws internal_error
	{
		not_null(other);
		_elements = (BitSet) other._elements.clone();
	}

	/*-----------------------------------------------------------*/
	/*--- (Access to) Static (Class) Variables ------------------*/
	/*-----------------------------------------------------------*/

	/** Constant for the empty set. */
	public static final terminal_set EMPTY = new terminal_set();

	/*-----------------------------------------------------------*/
	/*--- (Access to) Instance Variables ------------------------*/
	/*-----------------------------------------------------------*/

	/** Bitset to implement the actual set. */
	protected BitSet _elements;

	/*-----------------------------------------------------------*/
	/*--- General Methods ----------------------------------------*/
	/*-----------------------------------------------------------*/

	/** Helper function to test for a null object and throw an exception if
	 *  one is found. 
	 * @param obj the object we are testing.
	 */
	protected void not_null(Object obj) throws internal_error
	{
		if (obj == null)
			throw new internal_error("Null object used in set operation");
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Determine if the set is empty. */
	public boolean empty()
	{
		return equals(EMPTY);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Determine if the set contains a particular terminal. 
	 * @param sym the terminal symbol we are looking for.
	 */
	public boolean contains(terminal sym)
			throws internal_error
	{
		not_null(sym);
		return _elements.get(sym.index());
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Given its index determine if the set contains a particular terminal. 
	 * @param indx the index of the terminal in question.
	 */
	public boolean contains(int indx)
	{
		return _elements.get(indx);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Determine if this set is an (improper) subset of another.
	 * @param other the set we are testing against.
	 */
	public boolean is_subset_of(terminal_set other)
			throws internal_error
	{
		not_null(other);

		/* make a copy of the other set */
		BitSet copy_other = (BitSet) other._elements.clone();

		/* and or in */
		copy_other.or(_elements);

		/* if it hasn't changed, we were a subset */
		return copy_other.equals(other._elements);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Determine if this set is an (improper) superset of another.
	 * @param other the set we are testing against.
	 */
	public boolean is_superset_of(terminal_set other)
			throws internal_error
	{
		not_null(other);
		return other.is_subset_of(this);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Add a single terminal to the set.  
	 * @param sym the terminal being added.
	 * @return true if this changes the set.
	 */
	public boolean add(terminal sym)
			throws internal_error
	{
		boolean result;

		not_null(sym);

		/* see if we already have this */
		result = _elements.get(sym.index());

		/* if not we add it */
		if (!result)
			_elements.set(sym.index());

		return result;
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Remove a terminal if it is in the set.
	 * @param sym the terminal being removed.
	 */
	public void remove(terminal sym)
			throws internal_error
	{
		not_null(sym);
		_elements.clear(sym.index());
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Add (union) in a complete set.  
	 * @param other the set being added.
	 * @return true if this changes the set.
	 */
	public boolean add(terminal_set other)
			throws internal_error
	{
		not_null(other);

		/* make a copy */
		BitSet copy = (BitSet) _elements.clone();

		/* or in the other set */
		_elements.or(other._elements);

		/* changed if we are not the same as the copy */
		return !_elements.equals(copy);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Determine if this set intersects another.
	 * @param other the other set in question.
	 */
	public boolean intersects(terminal_set other)
			throws internal_error
	{
		not_null(other);
		return _elements.intersects(other._elements);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Equality comparison. */
	public boolean equals(terminal_set other)
	{
		if (other == null)
			return false;
		else
			return _elements.equals(other._elements);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Generic equality comparison. */
	public boolean equals(Object other)
	{
		if (!(other instanceof terminal_set))
			return false;
		else
			return equals((terminal_set) other);
	}

	/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

	/** Convert to string. */
	public String toString()
	{
		String result;
		boolean comma_flag;

		result = "{";
		comma_flag = false;
		for (int t = 0; t < terminal.number(); t++)
		{
			if (_elements.get(t))
			{
				if (comma_flag)
					result += ", ";
				else
					comma_flag = true;

				result += terminal.find(t).name();
			}
		}
		result += "}";

		return result;
	}

	/*-----------------------------------------------------------*/

}
