/* Copyright (C) 2008  Hugh McGuire   http://www.cis.gvsu.edu/~mcguire/
 *                     Grand Valley State University, MI  49401-9403
 */
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

For a copy of the GNU General Public License, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth floor, Boston, MA  02110-1301, USA.
Another option as of January 1, 2008 is to browse the following URL:
    http://www.gnu.org/copyleft/gpl.html
*/



import java.util.*;
import java.awt.FontMetrics;

/**
 * Representation and fundamental actions for mathematical expressions.
 *	fundamental/low-level		actions/utilities
 * @author Hugh McGuire
 */
class Expression implements Grammar_Construct, java.io.Serializable {

    // |Entry| avoids sharing |Expression| substructures
    // by providing |clone|, not the actual |Expression| in |Entry|.
    // (C++ would use |const|.)

    // I'm tempted to make this an old-fashioned public-data record class.

    // It's difficult to make fields |final| with |clone()| needing to reset
    // them (with |clone()| done properly
    // according to documentation ("The Java Programming Language")).

    private int context_begin;
    private Symbol operator;
    private Expression argument1, argument2;
    private int context_limit;

    String context_range() {
        return  "[" + context_begin + ',' + context_limit + ')';
        }

    String debug_info0() {
        return
            super.toString()
            + ": "
            + context_range()
            + " op: \""
            + operator.get_text()
            + "\" ("
            + operator.get_specifier()
            + ')';
	}

    String debug_info() { return  debug_info(""); }

    String debug_info(String indentation) {
        return
            indentation
	    + debug_info0()
            + '\n'
            + indentation 
            + "argument1:"
            + (argument1 == null
                ?  " " + argument1 + '\n'
                :  '\n' + argument1.debug_info(indentation + "    ")
                )
            + indentation 
            + "argument2:"
            + (argument2 == null
                ?  " " + argument2 + '\n'
                :  '\n' + argument2.debug_info(indentation + "    ")
                );
        }



    // 'macro' (;-)
    boolean op_spec_equals(Symbol.Specifier specifier) {
        return  operator.get_specifier().equals(specifier);
        }

    // 'macro' (;-)
    boolean ungrp_op_spec_equals(Symbol.Specifier specifier) {
	return  ungroup().op_spec_equals(specifier);
        }

    /*
    boolean is_negation() {
	return
	    op_spec_equals(Symbol.Specifier.NOT)
	    ||  use_prime_for_not  &&  op_spec_equals(Symbol.Specifier.APOSTROPHE);
	}
    boolean is_negation() { return  op_spec_equals(Symbol.Specifier.NOT); }
    */

    // 'macro' (;-)
    boolean op_is_alphabetic() {
        return
	    !operator.get_text().equals("")
            && Character.isUnicodeIdentifierStart(operator.get_text().charAt(0))
	    ;
        }


    Expression(int begin_given,
                Symbol operator_given,
                Expression argument1_given,
                Expression argument2_given,
                int limit_given
                )
        {
        context_begin = begin_given;
        operator = operator_given;
        argument1 = argument1_given;
        argument2 = argument2_given;
        context_limit = limit_given;
        }

    Expression(Symbol operator_given) {
        this(operator_given.get_begin(),
                operator_given,
                null,
                null,
                operator_given.get_limit()
                );
        }

    Expression(Expression argument1_given, Symbol operator_given) {
        this( argument1_given.get_begin(),
                operator_given,
                argument1_given,
                null,
                operator_given.get_limit()
                );
        }

    Expression(Symbol operator_given,
                        Expression argument1_given,
                        Expression argument2_given
                        )
        {
        this(
            operator_given.is_unary_prefix()
                ? operator_given.get_begin()
                : argument1_given.get_begin(),
            operator_given,
            argument1_given,
            argument2_given,
            argument2_given != null
                ? argument2_given.get_limit()
                : argument1_given != null
                    ? argument1_given.get_limit()
                    : operator_given.get_limit()
            );
        }


    Expression(Symbol operator_given, Expression argument1_given) {
        this(operator_given, argument1_given, null);
        }

    /*
    Expression(Symbol.Specifier op_specifier_given,
                Expression argument1_given,
                Expression argument2_given
                )
        {
	/ *
        this(
	    new Symbol(
		    use_prime_for_not
			&&  op_specifier_given == Symbol.Specifier.NOT
		    ? Symbol.Specifier.APOSTROPHE
		    : op_specifier_given
		    ),
	    argument1_given,
	    argument2_given
	    );
	* /
	this(new Symbol(op_specifier_given), argument1_given, argument2_given);
        }
    */

    /*
    Expression(Symbol.Specifier op_specifier_given, Expression argument1_given)
	{
        this(op_specifier_given, argument1_given, null);
        }

    Expression(Symbol.Specifier op_specifier_given) {
        this(op_specifier_given, null, null);
        }
    */

    Expression(int val_given) {
        this(new Symbol(val_given), null, null);
        }

    Expression(String identifier_given) {
        this(new Symbol(Symbol.Specifier.IDENTIFIER, identifier_given));
        }

    Expression(String identifier_given, Expression argument1_given) {
        this(new Symbol(Symbol.Specifier.IDENTIFIER, identifier_given),
                argument1_given,
                null
                );
        }


    public int get_begin() { return context_begin; }

    public Symbol get_operator() { return operator; }

    public Expression get_argument1() { return  argument1; }

    public Expression get_argument2() { return  argument2; }

    public Expression get_argument() {
	assert  argument2 == null  :  "argument2 == null";
	return  argument1;
	}

    public int get_limit() { return context_limit; }

    public Object clone() {
        Expression result;   
        try {
            result = (Expression) super.clone();
            }
        catch ( CloneNotSupportedException exception ) {
            throw new InternalError(exception.toString());
            }
        result.operator = (Symbol) operator.clone();
        if ( argument1 != null )
            result.argument1 = (Expression) argument1.clone();
        if ( argument2 != null )
            result.argument2 = (Expression) argument2.clone();
        return  result;
        }

    // Yes, I chose this name considering its similarity to the
    // expression "context switch". (;-)
    // Incidentally, aliasing has caused problems here:
    // |context_shift()| shifted an aliased node
    // beyond the length of the text!
    public void context_shift(int increment) {
        context_begin += increment;
        operator.context_shift(increment);
        if ( argument1 != null )
            argument1.context_shift(increment);
        if ( argument2 != null )
            argument2.context_shift(increment);
        context_limit += increment;
        }


    // Having these |this_structure_replace()| methods return |true| is a
    // ~~kludge to shorten some code that uses them and needs to get |true|.

    boolean this_structure_replace(
            Symbol operator_replacing,
            Expression argument1_replacing,
            Expression argument2_replacing
            )
        {
        operator = operator_replacing;
        argument1 = argument1_replacing;
        argument2 = argument2_replacing;
	/*
	// Code in |Ded_Action.java| invoking |Entry.try_select()|
	// depends on this not changing |context_begin| nor |context_limit|.
	*/
        return  true;
        }

    /*
    boolean this_structure_replace(
            Symbol.Specifier op_spec_replacing,
            Expression argument1_replacing,
            Expression argument2_replacing
            )
        {
        return  this_structure_replace(
		    / *
		    new Symbol(
			    use_prime_for_not
				&&  op_spec_replacing == Symbol.Specifier.NOT
			    ? Symbol.Specifier.APOSTROPHE
			    : op_spec_replacing
			    ),
		    * /
		    new Symbol(op_spec_replacing),
                    argument1_replacing,
                    argument2_replacing
                    );
        }

    boolean this_structure_replace(
            Symbol.Specifier op_spec_replacing,
            Expression argument1_replacing
            )
        {
        return
            this_structure_replace(op_spec_replacing, argument1_replacing, null)
                ;
        }

    boolean this_structure_replace(Symbol.Specifier specifier_replacing) {
        return this_structure_replace(specifier_replacing, null, null);
        }
    */

    boolean this_structure_replace(Expression other) {
        return
            this_structure_replace(
                other.operator, other.argument1, other.argument2
                );
        }

    boolean this_structure_replace(
            Symbol operator_replacing,
            Expression argument1_replacing
            )
        {
        return
            this_structure_replace(
		operator_replacing,
		argument1_replacing,
		null
		);
        }

    boolean this_structure_replace(Symbol operator_replacing) {
        return  this_structure_replace(operator_replacing, null, null);
        }

    boolean this_structure_replace(int val) {
        return  this_structure_replace(new Symbol(val));
        }


    boolean occurs(String id) {
	assert
	    // (argument2 != null  ==>  argument1 != null)
	    argument1 != null  ||  argument2 == null
	    : "argument1 != null  ||  argument2 == null";
	return
	    operator.get_text().equals(id)
	    ||  argument1 != null  &&  argument1.occurs(id)
	    ||  argument2 != null  &&  argument2.occurs(id);
        }

    // |boolean| enables one statement to operate and |return|
    // |ungrp| to avoid replacing argument parentheses
    // Each new replacement that is an |Expression| is a new clone.
    boolean replace_throughout_ungrp(Expression replacee, Object replacement) {
	return
	    !is_group()  &&  equivalent_form(replacee)
	    ? this_structure_replace(
		replacement instanceof Expression
		?  (Expression) ((Expression) replacement).clone()
		/*
		else if ( replacement instanceof Symbol.Specifier )
		    this_structure_replace(
			new Expression((Symbol.Specifier) replacement)
			);
		*/
		: replacement instanceof Symbol
		    ? new Expression((Symbol) replacement)
		    : new Expression(
			new Symbol(
			    Symbol.Specifier.IDENTIFIER,
			    (String) replacement
			    )
			)
		)
		// Regarding the parentheses around the  &&  here,
		// it surprised me that  |  has precedence stronger than  && .
	    : (argument1 != null
		    &&  argument1
			.replace_throughout_ungrp(replacee, replacement)
		    )
		| (argument2 != null
		    &&  argument2
			.replace_throughout_ungrp(replacee, replacement)
		    );
		// Could change that since arg1==null ==> arg2==null,
		// but (1) the resultant code might appear less symmetrical
		// and pretty, and (2) what if the situation changes in future?
        }


    /* Handles |replacement|s that are |Expression|s or |String|s.
     * Each new replacement that is an |Expression| is a new clone.
     * @return |true| if do a change, |false| otherwise
     * though as of 2007:June:21, no code used this returned information
     */
    boolean replace_throughout_scope(String replacee_id, Object replacement) {
        if ( operator.get_text().equals(replacee_id) ) {
            if ( replacement instanceof Expression )
                this_structure_replace(
                    (Expression) ((Expression) replacement).clone()
                    );
	    /*
            else if ( replacement instanceof Symbol.Specifier )
                this_structure_replace(
                    new Expression((Symbol.Specifier) replacement)
                    );
	    */
            else if ( replacement instanceof Symbol )
                this_structure_replace(
                    new Expression((Symbol) replacement)
                    );
            else
                operator =
		    new Symbol(
			    Symbol.Specifier.IDENTIFIER,
			    (String) replacement
			    );
	    return  true;
            }
	assert
	    // (argument2 != null  ==>  argument1 != null)
	    argument1 != null  ||  argument2 == null
	    : "argument1 != null  ||  argument2 == null";
        if ( argument1 != null
		&& (!op_spec_equals(Symbol.Specifier.QUANT_SEP)
                    || !argument1.occurs(replacee_id)
                    )
		)
	    return
		argument1.replace_throughout_scope(replacee_id, replacement)
		    // It surprised me that  |  has precedence stronger than  &&
		|  (argument2 != null
		    &&
		    argument2.replace_throughout_scope(replacee_id, replacement)
		    )
		;
	return  false;
        }


    Expression get_subexpr(int subexpr_start, int subexpr_limit) {
        if ( context_begin == subexpr_start  &&  context_limit == subexpr_limit
                )
            return  this;
        if ( argument2 == null  ||  subexpr_start < argument2.context_begin )
            return  argument1.get_subexpr(subexpr_start, subexpr_limit);
        return  argument2.get_subexpr(subexpr_start, subexpr_limit);
        }

    	// get_smallest_subexpr_covering
    Expression get_subexpr_covering(int start, int limit) {
	assert  start <= limit  :  "start <= limit";
        if ( start < context_begin  ||  context_limit < limit )
            return  null;
	Expression subresult = null;
        if ( argument1 != null )
            subresult = argument1.get_subexpr_covering(start, limit);
	if ( subresult != null )
	    return  subresult;
        if ( argument2 != null )
            subresult = argument2.get_subexpr_covering(start, limit);
	if ( subresult != null )
	    return  subresult;
	return  this;
        }

    			// squeeze
			// allow_spaces
    String gen_text(boolean loose) {
        StringBuilder text_sb = new StringBuilder();
        gen_text(text_sb, loose);
        return  text_sb.toString();
        }

    String gen_text() { return  gen_text(true); }

    // BEYOND SPACING, ALSO NEED TO ADD DELIMITERS SOMETIMES

    // THIS REQUIRES MORE WORK/TUNING
    private void gen_text(StringBuilder accumulator, boolean loose) {
					// INCIDENTALLY NEED TO ENSURE
					// SPACES FOR ALPHABETIC OPS
        context_begin = accumulator.length();
	boolean space_basic = false;
        if ( operator.is_unary_prefix() ) {
            operator.set_context(accumulator.length());
            accumulator.append(operator.get_text());
	    // "not (...)" vs. "P(...)" or "lg(...)"
            if ( argument1 != null
                    && !operator.arg1_group_worthwhile()
                    && (op_is_alphabetic()
			|| op_spec_equals(Symbol.Specifier.SUM)
			|| op_spec_equals(Symbol.Specifier.PROD)
			)
                    )
		{
                accumulator.append(' ');
                // See also |Expression()| above re when delimiters
                // are added to arguments.
		space_basic = true;
		}
            }
        if ( argument1 != null ) {
	    space_basic |=
		loose
		&& (operator.spec_boolean()
			&& !op_spec_equals(Symbol.Specifier.DIVIDES)
			&& !op_spec_equals(Symbol.Specifier.QUANT_SEP)
			&& !op_spec_equals(Symbol.Specifier.NOT)
		    || operator.is_binary_infix()
			&& !op_spec_equals(Symbol.Specifier.QUANT_SEP)
			&& !op_spec_equals(Symbol.Specifier.EXP)
			&& !op_spec_equals(Symbol.Specifier.TIMES)
			&& !op_spec_equals(Symbol.Specifier.DIVIDES)
			&& !op_spec_equals(Symbol.Specifier.QUOTIENT)
			&& !op_spec_equals(Symbol.Specifier.SUBST)
			/*
			&& !op_spec_equals(Symbol.Specifier.BECOMES)
			*/
		    );
	    boolean space_aux =
		    space_basic
                    && !op_spec_equals(Symbol.Specifier.BMOD)
                    && !op_spec_equals(Symbol.Specifier.PLUS)
                    && !op_spec_equals(Symbol.Specifier.MINUS)
                    && !op_spec_equals(Symbol.Specifier.FOR)
                    && !op_spec_equals(Symbol.Specifier.TO)
                    && !op_spec_equals(Symbol.Specifier.EQUALS)
                    && !op_spec_equals(Symbol.Specifier.IS)
                    && (argument1 != null
			    &&  !argument1.op_spec_equals(operator.get_specifier())
                            &&  argument1.operator.is_binary_infix()
                        ||  argument2 != null
                            &&  !argument2.op_spec_equals(operator.get_specifier())
                            &&  argument2.operator.is_binary_infix()
                        );
	    boolean loose_arg =
		op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
		    ?  true
		    :  space_basic && !op_spec_equals(Symbol.Specifier.TO);
            argument1.gen_text(accumulator, loose_arg);
            if ( !operator.is_unary_prefix() ) {
                if ( space_basic && !op_spec_equals(Symbol.Specifier.COMMA) ) {
                    accumulator.append(' ');
                    if ( space_aux )
                        accumulator.append(' ');
                    }
                operator.set_context(accumulator.length());
                accumulator.append(operator.get_text());
                if ( space_basic
			&& !operator.is_unary_postfix()
			&& !op_spec_equals(Symbol.Specifier.PMOD)
			)
		    {
                    accumulator.append(' ');
                    if ( space_aux )
                        accumulator.append(' ');
                    }
                }
            if ( argument2 != null )
		argument2.gen_text(accumulator, loose_arg);
            }
	else if ( operator.is_unary_postfix() ) {
	    operator.set_context(accumulator.length());
	    accumulator.append(operator.get_text());
	    }
        context_limit = accumulator.length();
        }

    String try_adjust_lines_to_fit(int width, FontMetrics fontmetrics) {
        StringBuilder text_buf = new StringBuilder(gen_text());
        try_adjust_lines_to_fit(width, fontmetrics, text_buf, 0);
        return  text_buf.toString();
        }

    private static final int TALTF_BUF_SIZE = 500;
    // It would be nice to be able to have something static inside a function
    // as in C.  Or I suppose there may be some object-oriented way to
    // handle this.
    private static final char[] taltf_tmp_buf, taltf_indent_buf;
    static {
	taltf_tmp_buf = new char[TALTF_BUF_SIZE];
	taltf_indent_buf = new char[TALTF_BUF_SIZE];
	Arrays.fill(taltf_indent_buf, ' ');
	taltf_indent_buf[0] = '\n';
	}

    // @return the amount by which this method lengthens this expression's text
    // NEEDS MORE WORK: STRESS-TEST IT SQUEEZING WIDTH
    // E.G. WITH |&not;(&forall;x)(&exist;y)[x + y = 2  &and;  2*x - y = 1]|
    // E.G. WITH RESULT OF APPLYING INDUCTION TO THE FOLLOWING:
    // |(An)[(SUM i for 1 <= i <= n) = n*(n+1)/2]
    private int try_adjust_lines_to_fit(
                    int width,
		    FontMetrics fontmetrics,
                    StringBuilder text_buf,
                    int indent
                    )
        {
        if ( fontmetrics.charsWidth(taltf_indent_buf, 1, indent) > width )
	    // hopeless
	    return  0;
	System.arraycopy(taltf_indent_buf, 1, taltf_tmp_buf, 0, indent);
	text_buf.getChars(context_begin, context_limit, taltf_tmp_buf, indent);
        if ( fontmetrics.charsWidth(
			    taltf_tmp_buf,
			    0,
			    indent + (context_limit - context_begin)
			    )
		<=
		width
		)
	    return  0;
	int result;
	if ( op_spec_equals(Symbol.Specifier.QUANT_SEP) ) {
		    // should handle some other operators this way also
		    // e.g. as in the following:
		    /*
			material
			  + additionalmaterial
		    */
	    if ( argument2.op_spec_equals(Symbol.Specifier.QUANT_SEP) )
		// keep sequence of quantifiers in one line
		result = 0;
	    else {
		indent += 2;        // 2: magic number (;-/
		text_buf.insert(
			    operator.get_limit(),
			    taltf_indent_buf,
			    0,
			    result = 1 + indent
			    );
		argument2.context_shift(result);
		}
	    result +=
		argument2.try_adjust_lines_to_fit(
			    width,
			    fontmetrics,
			    text_buf,
			    indent
			    );
	    }
	else {
	    // I'm not sure whether the immediately following is appropriate:
	    if ( operator.is_unary_prefix() )
		indent += operator.get_limit() - operator.get_begin();
	    result =
		argument1
		    .try_adjust_lines_to_fit(width, fontmetrics, text_buf, indent);
	    if ( !operator.is_unary_prefix() ) {
		text_buf.insert(
			    argument1.context_limit,
			    taltf_indent_buf,
			    0,
			    1 + indent
			    );
		operator.context_shift(result += 1 + indent);
		if ( argument2 != null ) {
		    int spaces_following_operator = 0;
		    while ( text_buf.charAt(
					operator.get_limit()
					+
					spaces_following_operator
					)
				==
				' '
				)
			spaces_following_operator++;
		    text_buf.delete(operator.get_limit(),
				    operator.get_limit() + spaces_following_operator
				    );
		    result -= spaces_following_operator;
		    text_buf.insert(
				operator.get_limit(),
				taltf_indent_buf,
				0,
				1 + indent
				);
		    result += 1 + indent;
		    // doing |argument2.context_shift(result)| below
		    // instead of here
		    // because of kludge with closing delimiter(s)
		    }
		}
	    if ( argument2 != null ) {
		argument2.context_shift(result);
		    // rem. closing delimiter kludge
		result +=
		    argument2.try_adjust_lines_to_fit(
			width, fontmetrics, text_buf, indent
			);
		}
	    }
	context_limit += result;
	return  result;
	}

    // FUTURE:
    // |alt_delims()| change nested delimiters to alternate "[([(...)])]"
    //   then need to sub-invoke |gen_text()| (or a version of it
    //   updating simply delimiters in text)

    public boolean equivalent_form(Grammar_Construct other_gc) {
	if ( other_gc == null )
	    return  false;
        if ( is_group() )
            return  argument1.equivalent_form(other_gc);
        // Consider checking class of |other_gc|
        // and if necessary throwing |IllegalArgumentException|.
        Expression other = (Expression) other_gc;
        if ( other.is_group() )
            return  equivalent_form(other.argument1);
        if ( op_spec_equals(Symbol.Specifier.QUANT_SEP) ) {
            if ( !other.op_spec_equals(Symbol.Specifier.QUANT_SEP) )
                return  false;
            Expression quantifier = argument1.ungroup();
            Expression other_quantifier = other.argument1.ungroup();
            if ( !quantifier.operator.equivalent_form(other_quantifier.operator)
                    )
                return  false;
            Expression quantifier_arg = quantifier.argument1;
            Expression other_quantifier_arg = other_quantifier.argument1;
	    /*
            if ( quantifier_arg.is_variable()
                    &&  other_quantifier_arg.is_variable()
                    )
		*/
                if ( quantifier_arg.equivalent_form(other_quantifier_arg) )
                    return  argument2.equivalent_form(other.argument2);
                else {
                    Expression arg2_ = (Expression) argument2.clone();
                    arg2_.replace_throughout_scope(
                        quantifier_arg.operator.get_text(),
                        other_quantifier_arg
                        );
                    return  arg2_.equivalent_form(other.argument2);
                    }
            // FUTURE: enable matching of more quantified variables
            }
        return
            operator.equivalent_form(other.operator)
            &&  (argument1 == null
                    ?  other.argument1 == null
                    :  argument1.equivalent_form(other.argument1)
                    )
            &&  (argument2 == null
                    ?  other.argument2 == null
                    :  argument2.equivalent_form(other.argument2)
                    )
            ;
        }

    // TO AVOID TRIPPING MYSELF UP INVOKING THIS ACCIDENTALLY,
    // SHOULD DEFINE THIS TO THROW SOME APPROPRIATE EXCEPTION
    // (I think there are API things that throw exceptions indicating
    // that they should never be used.  Incidentally, compare to
    // issues in C++ with copy constructor, |operator=()|, ....)
    /* I'm not sure whether some code still uses |Expression.equals()|.
     * (When I deactivated this, I was tripped up by code in |simplify()|
     * which invoked |LinkedList<Expression>.indexOf()| which used it.)
    public boolean equals(Object other_obj) {
        if ( getClass() != other_obj.getClass() )
            return  false;
        return  equivalent_form((Expression) other_obj);
        }
    */

    /*
    public boolean equivalent_form_index_in(List<Expression> el) {
	}
    */

    public boolean equivalent_form_contained_in(
	    Collection<Expression> expr_coll
	    )
	{
	for ( Expression expr : expr_coll )
	    if ( equivalent_form(expr) )
		return  true;
	return  false;
	}

    HashSet<String> var_seq_cull_duplicates() {
        HashSet<String> result = new HashSet<String>();
        if ( !op_spec_equals(Symbol.Specifier.COMMA) ) {
            result.add(operator.get_text());
            return  result;
            }
        result = argument2.var_seq_cull_duplicates();
        if ( result.contains(argument1.operator.get_text()) )
            this_structure_replace(argument2);
        else
            result.add(argument1.operator.get_text());
        return  result;
        }

    Expression var_seq_remove(HashSet<String> removing) {
        if ( removing.contains(operator.get_text()) )
            return  null;
        if ( op_spec_equals(Symbol.Specifier.IDENTIFIER) )
            return  this;
        argument1 = argument1.var_seq_remove(removing);
        argument2 = argument2.var_seq_remove(removing);
        if ( argument1 == null  &&  argument2 == null )
            return  null;
        if ( argument2 == null )
            return  argument1;
	if ( argument1 == null )
	    return  argument2;
        return  this;
        }

    Expression var_seq_remove(String v) {
	HashSet<String> set_v = new HashSet<String>();
	set_v.add(v);
	return  var_seq_remove(set_v);
	}

    // CONSIDER MOVING THIS TO |Expressions_Mgmt.java|
    void clear_excessive_basic_grps_internal() {
        if ( argument1 != null )
	    if ( operator.arg1_group_worthwhile()
		    &&  argument1.is_basic_group()
		    )
		{
		argument1.argument1.clear_excessive_basic_grps_internal();
		if ( argument1.argument1.is_basic_group() )
			/*
			&& !argument1.get_operator().get_text()
			    .equalsIgnoreCase("IF")
			*/
		    argument1.argument1.this_structure_replace(
			    argument1.argument1.argument1
			    );
		}
	    else {
		argument1.clear_excessive_basic_grps_internal();
		if ( argument1.is_basic_group()
			&&  argument1.argument1.argument1 == null
			&&  argument1.argument1.argument2 == null
			)
		    argument1.this_structure_replace(argument1.argument1);
		}
        if ( argument2 != null ) {
            argument2.clear_excessive_basic_grps_internal();
	    /*
            assert
		!operator.arg1_group_worthwhile()
		: "!operator.arg1_group_worthwhile()";
	    */
            if ( argument2.is_basic_group()
                    &&  argument2.argument1.argument1 == null
                    &&  argument2.argument1.argument2 == null
                    )
                argument2.this_structure_replace(argument2.argument1);
	    }
	switch ( operator.get_specifier() ) {
	    case TIMES:
		// Fix what this method may have done,
		// e.g. "5(3)" changed to "53", "k(2)" changed to "k2".
		// Not simplifying here because it may be desirable to
		// see unsimplified material.
		if ( operator.get_text().equals("")
			&&  argument2.op_spec_equals(Symbol.Specifier.INT)
			    // Consider also:  || arg1.op is 1
			)
		    this_structure_replace(
			new Symbol(Symbol.Specifier.TIMES,
				    Character.toString(
					    Symbol.MIDDLE_DOT_UNICODE
					    )
				    ),
			argument1,
			argument2
			);
		break;

	    case OPENING_DELIMITER:
		if ( argument1
			    .op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
			)
		    {
		    if ( is_basic_group() )
			this_structure_replace(argument1);
		    else if ( argument1.is_basic_group() )
			argument1.this_structure_replace(argument1.argument1);
		    }	// pedagogical note: when I added the following clause,
			// I forgot this brace pair ... (:-/
		else if ( is_group()
			    && (argument1.operator.is_unary_postfix()
				||  argument1.op_spec_equals(
					    Symbol.Specifier.EXP
					    )
				    &&  argument1.operator.get_text().equals("")
				    &&  argument1.argument2.op_spec_equals(
						Symbol.Specifier.INT
						)
				    // the following should be unnecessary
				    // since I've been having EXP:""
				    // precisely when the exponent is superscr.
				    &&  argument1.argument2.operator
					    .text_is_superscript()
				)
			)
		    this_structure_replace(argument1);
		break;
	    }
        }


    /*
    LinkedList<Expression> multiary_op_args_ungroup() {
        assert  argument1 != null  &&  argument2 != null
                : "argument1 != null  &&  argument2 != null";
        LinkedList<Expression> result = new LinkedList<Expression>();
        multiary_op_args_ungroup(result);
        return  result;
        }

    private void multiary_op_args_ungroup(LinkedList<Expression> accumulator) {
        for ( Expression arg_ungroup = argument1.ungroup();
                arg_ungroup != null;
                arg_ungroup =
                    arg_ungroup == argument1.ungroup()
                    ? argument2.ungroup()
                    : null
                )
            if ( arg_ungroup.operator.equivalent_form(operator) )
                arg_ungroup.multiary_op_args_ungroup(accumulator);
            else
                accumulator.add(arg_ungroup);
        }
    */

    private void multiary_op_args(
	    Symbol.Specifier op,
	    LinkedList<Expression> accumulator
	    )
	{
	if ( op_spec_equals(op) ) {
	    	// Applying |ungroup()| to |argument1| and |argument2| here
		// might save some invocations of this function
		// but then there'd be invocations of |ungroup()|.
	    argument1.multiary_op_args(op, accumulator);
	    argument2.multiary_op_args(op, accumulator);
	    }
		    // Note how the grouping will be preserved otherwise.
	else if ( is_group() && argument1.ungroup().op_spec_equals(op) )
	    argument1.ungroup().multiary_op_args(op, accumulator);
	else
	    accumulator.add(this);
	}

    LinkedList<Expression> multiary_op_args(Symbol.Specifier op) {
        LinkedList<Expression> result = new LinkedList<Expression>();
        multiary_op_args(op, result);
        return  result;
        }

    LinkedList<Expression> multiary_op_args() {
	return  multiary_op_args(operator.get_specifier());
	}

    // ~~macro
    // so has same behavior at error as |Symbol.get_val()|
    int get_val() { return  operator.get_val(); }

    boolean some_sub_junct_matches(Expression expr) {
	return
	    argument1.ungroup().equivalent_form(expr)
	    ||  argument2.ungroup().equivalent_form(expr)
	    ||  argument1.ungroup().op_spec_equals(operator.get_specifier())
		&&  argument1.ungroup().some_sub_junct_matches(expr)
	    ||  argument2.ungroup().op_spec_equals(operator.get_specifier())
		&&  argument2.ungroup().some_sub_junct_matches(expr);
	}

    boolean is_basic_group() {
        return
	    op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
	    && (operator.get_text().equals("(")
		|| operator.get_text().equals("[")
		);
	}

    boolean is_group() {
        return
	    is_basic_group()
	    ||
	    op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
	    && (operator.get_text().equals("")
		|| operator.get_text().equalsIgnoreCase("IF")
		);
	}

    Expression parenthesize() {
        return
            new Expression(new Symbol(Symbol.Specifier.OPENING_DELIMITER, "("),
                            this,
                            new Expression(
                                    new Symbol(
                                            Symbol.Specifier.CLOSING_DELIMITER,
                                            ")"
                                            )
                                    )
                            );
        }

    Expression bracket() {
        return
            new Expression(new Symbol(Symbol.Specifier.OPENING_DELIMITER, "["),
                            this,
                            new Expression(
                                    new Symbol(
                                            Symbol.Specifier.CLOSING_DELIMITER,
                                            "]"
                                            )
                                    )
                            );
        }

    // 'prefers' parenthesizing;
    // some code e.g. in |Ded_Action.java| depends on this 'preference'
    Expression ensure_group() {
        if ( op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
                || op_spec_equals(Symbol.Specifier.FALSE)       // ~~kludge
                || op_spec_equals(Symbol.Specifier.TRUE)        // ~~kludge
                )
            return this;
        if ( argument1 != null
                    &&  argument1.op_spec_equals(
			    Symbol.Specifier.OPENING_DELIMITER
			    )
                    &&  argument1.operator.get_text().equals("(")
                ||  argument2 != null
                    &&  argument2.op_spec_equals(
			    Symbol.Specifier.OPENING_DELIMITER
			    )
                    &&  argument2.operator.get_text().equals("(")
                )
	    return  bracket();
	return  parenthesize();
        }

    Expression ungroup() {
	return  is_group() ? argument1.ungroup() : this;
        }

    Expression un_if() {
	return
	    op_spec_equals(Symbol.Specifier.OPENING_DELIMITER)
		&&  operator.get_text().equalsIgnoreCase("IF")
	    ?  argument1
	    :  this;
	}

    }
