/*
 * $Id: Optimizer.java,v 1.7 1998/11/22 14:20:14 metlov Exp $
 *
 * This file is part of the Java Expressions Library (JEL).
 *   For more information about JEL visit :
 *    http://galaxy.fzu.cz/JEL/
 *
 * (c) 1998 by Konstantin Metlov(metlov@fzu.cz);
 *
 * JEL is Distributed under the terms of GNU General Public License.
 *    This code comes with ABSOLUTELY NO WARRANTY.
 *  For license details see COPYING file in this directory.
 */

package gnu.jel;

import java.util.Stack;
import gnu.jel.debug.Debug;
import gnu.jel.debug.Tester;
import java.lang.reflect.Method;

/**
 * This class handles storage of the expressions automatic widening type 
 * conversions and optimizations.
 * <P> Currently the only supported type of optimization is evaluation of
 * constant subexpressions. Unlike other compilers JEL not only evaluates
 * arithmetic operators on constants but also attempts to call some (those 
 * which marked stateless) functions if all their arguments are constants.
 * For example, expression "sin(1)" will be completely evaluated at compile
 * time.
 * <P> The gnu.jel.Library class handles details on which functions can be 
 * evaluated at compile time and which can not, see it's documentation for
 * details.
 * @see gnu.jel.Library
 * @author Konstantin L. Metlov (metlov@fzu.cz)
 */
public class Optimizer {

  static Class string_class=null;
  
  static {
    try {
      string_class=Class.forName("java.lang.String");
    } catch (ClassNotFoundException e2) {
      if (Debug.enabled)
	Debug.println("Get Yourself a real JAVA.");
      // Can't be in java
    };
  };
  
  private Library lib;

  private Stack types=new Stack();

  /**
   * Double linked list of operations in this expression.
   */
  protected OPlist code=new OPlist();
  private Stack functions=new Stack();
  private Stack functionsDescriptors=new Stack();  
  //  private Throwable caughtThrowable=null;
  
  private boolean finished=false;

  /**
   * Constructs the new "empty" optimizer with the library specified.
   * @param lib is the library, used for function names resolution.
   */
  public Optimizer(Library lib) {
    this.lib=lib;
  };


  /**
   * Generates a "load boolean constant" operation.
   * @param c is the constant to load.
   */
  public void load(boolean c) {
    load(Boolean.TYPE,new Boolean(c));
  };

  
  /**
   * Generates a "load byte constant" operation.
   * @param c is the constant to load.
   */
  public void load(byte c) {
    load(Byte.TYPE,new Byte(c));
  };

  /**
   * Generates a "load char constant" operation.
   * @param c is the constant to load.
   */
  public void load(char c) {
    load(Character.TYPE,new Character(c));
  };


  /**
   * Generates a "load short constant" operation.
   * @param c is the constant to load.
   */
  public void load(short c) {
    load(Short.TYPE,new Short(c));
  };


  /**
   * Generates a "load int constant" operation.
   * @param c is the constant to load.
   */
  public void load(int c) {
    load(Integer.TYPE,new Integer(c));
  };


  /**
   * Generates a "load long constant" operation.
   * @param c is the constant to load.
   */
  public void load(long c) {
    load(Long.TYPE,new Long(c));
  };

  /**
   * Generates a "load float constant" operation.
   * @param c is the constant to load.
   */
  public void load(float c) {
    load(Float.TYPE,new Float(c));
  };
  
  /**
   * Generates a "load double constant" operation.
   * @param c is the constant to load.
   */
  public void load(double c) {
    load(Double.TYPE,new Double(c));
  };

  /**
   * Generates a "load String constant" operation.
   * @param s is the String constant to load.
   */
  public void load(String s) {
    load(string_class,s);
  };
  
  private void load(Class t, Object o) {
    seeNonFinished();
    OP nop = new OP_load(t,o);
    types.push(t);
    code.addLast(nop);
  };
  
  /**
   * Generates an explicit type conversion operation.
   * <P> It has the sense to call this function only for narrowing 
   * conversions (see &sect;5.1.3 and &sect;5.1.5 of the Java Language
   * Specification (JLS) ). 
   * Widening ( &sect;5.1.2,  &sect;5.1.4 of JLS) type conversions are 
   * performed by this compiler automatically.
   * @param to is the class to convert to.
   * @param widening if true prohibits doing narrowing conversions.
   * @exception IllegalStateException if requested conversion is not supported.
   */
  public void convert(Class to,boolean widening) throws IllegalStateException {
    seeNonFinished();
    Class ct=(Class)types.peek();
    
    if (!ExpressionImage.canConvert(ct,to))
      throw new IllegalStateException("Can not convert "+ct.toString()+
				      " to "+to.toString()+
				      ".");
    
    if (widening && !ExpressionImage.canConvertByWidening(ct,to))
      throw new IllegalStateException("You must specify narrowing conversion"+
				      " from "+ct.toString()+" to "+
				      to.toString()+" explicitly");
    
    if (ct!=to) { // need convert
      OP lastOP=code.getLast();
      if (lastOP instanceof OP_convert) { // can patch
	((OP_convert)lastOP).setType(to);
      } else 
	code.addLast(new OP_convert(to));
    };
    types.pop();
    types.push(to);
  };

  /**
   * Generates an explicit type conversion operation.
   * <P> This function is equivalent to <TT>convert(to,false)</TT>.
   * Widening ( &sect;5.1.2,  &sect;5.1.4 of JLS) type conversions are 
   * performed by this compiler automatically.
   * @param to is the class to convert to.
   * @exception IllegalStateException if requested conversion is not supported.
   */
  public void convert(Class to) throws IllegalStateException {
    convert(to,false);
  };
  
  
  /**
   * Generates an unary operation.
   * <P> The only unary operation for now is the negation (invert sign) 
   * operation.
   * @param is the operation code, for now it can be only 
   * <TT>ExpressionImage.UN_NE</TT>.
   * @exception IllegalStateException if operation is not supported
   * for types in stack.
   */
  public void unary(int o) {
    seeNonFinished();
    Class  ct=(Class)types.peek();
    
    if (!ExpressionImage.canGenerateUnary(o,ct)) {
      throw new IllegalStateException("Unary "+ExpressionImage.unaryNames[o]+
				      " is not supported on "+ct.getName()+
				      "'s");
    };
    code.addLast(new OP_unary(o));
  };

  /**
   * Denotes start of group of logical operators whose result should be inverted.
   * @see gnu.jel.Optimizer#logical_not
   */
  public void logical_not_start() {
    seeNonFinished();
    OP_logical_not opc=new OP_logical_not();
    functions.push(opc);
    int[] descrs={0};
    functionsDescriptors.push(descrs);
    code.addLast(new OP_start(opc));
  };
  
  /**
   * Inverts result of group of logical operators.
   * <P> To generate logical not operation it is needed :
   * <PRE>
   * 1. logical_not_start
   * 2. calculate value to be inverted
   * 3. logical_not
   * </PRE>
   * @see gnu.jel.Optimizer#logical_not
   */
  public void logical_not() throws IllegalStateException {
    if (types.peek()!=Boolean.TYPE)
      throw new IllegalStateException("You tried to use logical complement"+
				      " on "+((Class)types.peek()).getName()+
				      ". This operation is supported only"+
				      " on booleans.");
    OP_logical_not opf=(OP_logical_not) functions.pop();
    int[] descrs=(int[])functionsDescriptors.pop();
    code.addLast(opf);
  };

  
  /**
   * Denotes the start of the function call.
   * <P> Example of the sequence of method calls to perform (compile)
   * the function invocation is given in the description of
   * gnu.jel.Optimizer.function_call(...) method.
   * @see gnu.jel.Optimizer#function_call
   */
  public void function_start() {
    seeNonFinished();
    OP_call opc=new OP_call();
    functions.push(opc);
    int[] descrs={0};
    functionsDescriptors.push(descrs);
    code.addLast(new OP_start(opc));
  };

  /**
   * Specifies that the parameter for the binary operation is now in stack.
   * <P> Example of the sequence of method calls to perform (compile)
   * the binary operation is given in the description of
   * gnu.jel.Optimizer.binaryOP() method.
   * @see gnu.jel.Optimizer#binaryOP
   */
  public void binaryOP_param() {
    seeNonFinished();
    OP_binary opc=new OP_binary();
    functions.push(opc);
    int[] descrs={0};
    functionsDescriptors.push(descrs);
    code.addLast(new OP_param(opc,(Class)types.peek()));
  };

  /**
   * Specifies that the parameter for the function is now in stack.
   * <P> Example of the sequence of method calls to perform (compile)
   * the function is given in the description of
   * gnu.jel.Optimizer.function_call(...) method.
   * @see gnu.jel.Optimizer#function_call
   */
  public boolean function_param() {
    seeNonFinished();
    OP_function opf=(OP_function) functions.peek();
    int[] descrs=(int[]) functionsDescriptors.peek();
    descrs[0]++;
    
    code.addLast(new OP_param(opf,(Class)types.peek()));
    return true; 
  };

  /**
   * Generates the function call.
   * Example : "how to call the function"
   * <PRE>
   * optimizer.function_start();
   * optimizer.load(2L);
   * optimizer.function_param();
   * optimizer.load(2.0);
   * optimizer.function_param();
   * optimizer.function_call("name_in_the_library");
   * </PRE>
   * The function name is searched in the library. If there are
   * several applicable functions in the library with the same name (not
   * necessary in the same object originally) the most specific one is
   * called ( See &sect; 15.11.2.2 in the Java Language Specification). Types
   * conversion takes place automatically.
   * @param name is the name of the function to call.
   * @exception  IllegalStateException if no function with the given name
   * and parameter types in stack can be found in the library.
   */
  public void function_call(String name) throws IllegalStateException {
    seeNonFinished();
    OP_call opf=(OP_call) functions.peek();
    int[] descrs=(int[])functionsDescriptors.peek();
    
    int nparams=descrs[0];
    Class[] paramTypes=new Class[nparams];
    for(int i=nparams-1;i>=0;i--) paramTypes[i]=(Class)types.pop();

    Method m;
    try {
      m=lib.getMethod(name,paramTypes);
    } catch (NoSuchMethodException e) {
      // push parameters back
      for(int i=0;i<nparams;i++) types.push(paramTypes[i]);
      throw new IllegalStateException(e.getMessage());
    };
    
    OP_call opc=(OP_call)opf;
    code.addLast(opc);
    
    opc.setMethod(code,m,lib.getDynamicMethodClassID(m),lib.isStateless(m));
    
    types.push(m.getReturnType());
    functions.pop(); functionsDescriptors.pop();
  };

  /**
   * Generates a binary operation.
   * <P> The binary operation codes are defined in 
   * <TT>gnu.jel.ExpressionImage</TT> as constants BI_XX .
   * <P> Example : "how to sum up two numbers with this optimizer"
   * <PRE>
   * optimizer.load(2L);
   * optimizer.binaryOP_param();
   * optimizer.load(2.0);
   * optimizer.binaryOP(ExpressionImage.BI_PL);
   * </PRE>
   * Note different types of operands, conversion ( to double) will be
   * performed automatically. Note also binaryOP_param() usage, its use is
   * obligatory to denote that the parameter for subsequent binary operation
   * is now ion stack.
   * @param is the operation code, see above.
   * @exception  IllegalStateException if this binary op is not supported
   * for the types in stack.
   * @see gnu.jel.ExpressionImage
   */
  public void binaryOP(int o,boolean logical) throws IllegalStateException {
    seeNonFinished();
    OP_binary opf=(OP_binary) functions.peek();
    int[] descrs=(int[])functionsDescriptors.peek();
    
    OP_param terminating_param=new OP_param(opf,(Class)types.peek());
    terminating_param.setbinaryparam=false;
    code.addLast(terminating_param);
    
    Class op2=(Class)types.pop();
    Class op1=(Class)types.pop();

    // Catch possible errors
    String message=null;
    boolean error=false;

    if (!logical) {
      if (!ExpressionImage.canGenerateBinary(o,op1,op2)) {
	error=true;
	message="Types "+op1.toString()+" and "+ op2.toString()+
	  " are not suitable "+"for the operation \""+
	  ExpressionImage.binaryNames[o]+"\"";
      };
    } else {
      if ((op1!=Boolean.TYPE) || (op2!=Boolean.TYPE)) {
	error=true;
	message="Types "+op1.toString()+" and "+ op2.toString()+
	  " are not suitable for \""+ExpressionImage.logicalNames[o]+
	  "\" operation. Both operands must be boolean.";
      };
    };
    
    if (error) {
      types.push(op1);
      types.push(op2);
      throw new IllegalStateException(message);
    };
    // all errors reported
    
    // Determine types of operands and result
    Class opType=Boolean.TYPE,resType=Boolean.TYPE;
    if (!logical) {
      if (ExpressionImage.isPromotionBinary(o)) {
	if ((o==ExpressionImage.BI_PL) && (op1==string_class))
	  opType=string_class;
	else
	  opType=ExpressionImage.getBinaryPromoted(op1,op2);
      } else {
	opType=resType=ExpressionImage.getUnaryPromoted(op1);
      };
      resType=opType;
      if ((o>=ExpressionImage.BI_EQ) && (o<=ExpressionImage.BI_LE)) 
	resType=Boolean.TYPE;
    };
    
    // Note operation
    OP_binary opc=(OP_binary)opf;    
      
    code.addLast(opc);
    
    opc.setOperation(code,o,logical,opType);
      
    types.push(resType);
    functions.pop(); functionsDescriptors.pop();
  };

  /**
   * Starts generation of conditional ?: .
   * <P> The stack should contain boolean value.
   * <P> To generate complete conditional it is needed :
   * <PRE>
   * 1. calculate condition (boolean)
   * 2. conditional_true()
   * 3. add instructions to executed if condition is "true"
   * 4. conditional_false()
   * 5. add instructions to executed if condition is "false"
   * 6. conditional_end()
   * </PRE>
   */
  public void conditional_true()  throws IllegalStateException {
    if (types.pop()!=Boolean.TYPE)
      throw new IllegalStateException("The first operand of conditional must"+
				      " be of boolean type.");

    OP_conditional opc=new OP_conditional();
    functions.push(opc);
    int[] descrs={0};
    functionsDescriptors.push(descrs);
    code.addLast(new OP_start(opc));
  };

  /**
   * Continues generation of conditional ?: .
   * @see gnu.jel.Optimizer#conditional_true
   */
  public void conditional_false() {
    seeNonFinished();
    OP_conditional opf=(OP_conditional) functions.peek();
    int[] descrs=(int[]) functionsDescriptors.peek();
    descrs[0]++;
    
    // Search for corresponding OP_start
    OP start=code.getLast();
    while (!((start instanceof OP_start) && 
	     (((OP_start)start).getFunction()==opf))) start=start.prev;
    
    // create list of instructions of true branch
    opf.setTrueList(code.cut_end(start.next));
  };

  /**
   * Finishes generation of conditional ?: .
   * @see gnu.jel.Optimizer#conditional_true
   */
  public void conditional_end()  throws IllegalStateException  {
    seeNonFinished();
    OP_conditional opf=(OP_conditional) functions.peek();
    int[] descrs=(int[]) functionsDescriptors.peek();
    descrs[0]++;

    // Search for corresponding OP_start
    OP start=code.getLast();
    while (!((start instanceof OP_start) && 
	     (((OP_start)start).getFunction()==opf))) start=start.prev;

    // create list of instructions of false branch
    opf.setFalseList(code.cut_end(start.next));
    
    // Now what remains is the start instruction. Take it out.
    code.remove(start);
    
    // Let's determine the type of result.
    // Now We have in stack results of both branches.
    
    Class op2=(Class)types.pop();
    Class op1=(Class)types.pop();

    // According to JLS 15.24
    Class resType=null;
    if (op1.isPrimitive() && op2.isPrimitive())
      resType=ExpressionImage.getBinaryPromoted(op1,op2);
    else {
      if (op1==op2) resType=op1;
      else if (ExpressionImage.canConvertByWidening(op1,op2)) resType=op2;
      else if (ExpressionImage.canConvertByWidening(op2,op1)) resType=op1;
    };
    
    if (resType==null)
      throw new IllegalStateException("Operands of conditional have types "+
				      op1.getName()+" and "+op2.getName()+
				      " . These types are not compatible.");
    opf.setType(resType);
    types.push(resType);

    code.addLast(opf);
    functions.pop(); functionsDescriptors.pop();    
  };

  
  /**
   * Finishes the function.
   * <P> This method should be called exactly once before the attempt
   * to instantiate expression (or obtain its image) is made.
   * proper state to be finished (some extra items are in stack).
   */
  public void finish() {
    seeNonFinished();
    if (Debug.enabled) {
      Debug.assert((code.size==0) || (types.size()==1),
		   "Stack should contain single item on exit.");
      Debug.assert((functions.size() ==0) && (functionsDescriptors.size() ==0),
		   "There are function calls in progress.");
    };
    finished=true;
  };
  
  /**
   * Optimizes the function.
   * <P> Currently only evaluation of constant subexpressions is performed.
   * @param of is the maximum number of optimization iterations to perform.
   */
  public void optimize(int of) {
    seeFinished();
    while ((of>0) && optimizeIteration(code)) of--;
  };
  
  /**
   * Performs one optimization pass on the given list of operations.
   * <P> Currently only evaluation of constant subexpressions is performed.
   * @param code is a linked list of operations to optimize.
   * @return true if additional optimization pass may simplify code further.
   */
  protected static boolean optimizeIteration(OPlist code) {
    return code.optimize();
  };

  /**
   * Compiles the expression.
   * @return The instance of the <TT>gnu.jel.CompiledExpression</TT> subclass,
   * with the function, represented by this optimizer compiled in.
   */
  public CompiledExpression compile() {
    return compileBits().getExpression();
  };

  /**
   * Compiles the expression into an ExpressionBits object.
   * <P>ExpressionBits is a wrapper allowing to store expression
   * in any <TT>java.io.OutputStream</TT> using Java serialization mechanism.
   * @return instance of the <TT>gnu.jel.ExpressionBits</TT> object.
   * @see gnu.jel.ExpressionBits
   */
  public ExpressionBits compileBits() {
    seeFinished();
    ExpressionImage image=new ExpressionImage();
    code.compile(image);
    image.asm_return();
    return image.getBits();
  };

  /**
   * Represents the expression, contained in this optimizer as <TT>String</TT>.
   * @return String representation of the expression.
   */
  public String toString() {
    return code.toString();
  };

  private void seeFinished() {
    if (!finished) throw new IllegalStateException("Attempt to instantiate "+
						   "unfinished function.");
  };

  private void seeNonFinished() {
    if (finished) throw new IllegalStateException("Attempt to modify "+
						  "finished function.");
  };

  //-----------------------TESTS----------------------------------
  
  /**
   * Performs unitary test of the interpreter.
   * <P> This function works only if the Debugging is turned "ON".
   * @param args ignored.
   * @see gnu.jel.debug.Debug#enabled
   */
  public static void main(String[] args) {
    if (Debug.enabled) {
      Tester t=new Tester(System.out);
      test(t);
      t.summarize();
    };
  };


  /**
   * Performs unitary test of the interpreter.
   * <P> This function works only if the Debugging is turned "ON".
   *  Used if all package is being tested and not just this class alone.
   * @param t Tester to report test results.
   * @see gnu.jel.debug.Debug#enabled
   */
  public static void test(Tester t) {
    if (Debug.enabled) {
      // Prepare library
      Library clib=null;
      try {
	Class[] statlb={Class.forName("java.lang.Math")};
	clib=new Library(statlb,null);
	clib.markStateDependent("random",null);
      } catch (Throwable e) {Debug.reportThrowable(e);};
      
      
      t.startTest("Enter \"(2.0, 2,*)\" ");
      try {
       	Optimizer op=new Optimizer(clib);
	op.load(2.0);
	op.binaryOP_param();
	op.load(2L);
	op.binaryOP(ExpressionImage.BI_MU,false);
	op.finish();
	Debug.println("");
	Debug.println(op.toString());
	CompiledExpression ce=op.compile();
	Number res=(Number)ce.evaluate(null);
	Debug.println("And the result is "+res.toString());
	if (res.intValue()==4) t.testOK(); else t.testFail();
      } catch (Throwable e) {
	Debug.reportThrowable(e);
	t.testFail();
      };

      t.startTest("Enter \"5+(2.0*2)*exp(0)\" ");
      Debug.println("");
      try {
       	Optimizer op=new Optimizer(clib);
	op.load(5L);
	op.binaryOP_param();
	op.load(2.0);
	op.binaryOP_param();
	op.load(2L);
	op.binaryOP(ExpressionImage.BI_MU,false);
	op.binaryOP_param();
	   op.function_start();
	   op.load(0);
	   op.function_param();
           op.function_call("exp");
	op.binaryOP(ExpressionImage.BI_MU,false);
	op.binaryOP(ExpressionImage.BI_PL,false);
	op.finish();
	Debug.println(op.toString());
	CompiledExpression ce=op.compile();
	Number res=(Number)ce.evaluate(null);
	Debug.println("And the result is "+res.toString());
	if (res.intValue()==9) t.testOK(); else t.testFail();
      } catch (Throwable e) {
	Debug.reportThrowable(e);
	t.testFail();
      };


    };
  };

  

  //--------------------------------------------------------------
 
};


// Below there are classes for the linked list (LL), representing
// expression. List is. probably, the most effective representation of the
// expression in the polish notation. And, well, I can get Scheme 
// representation of the expression easily :)

// JDK 1.2 will have the LL implementation built in, thus
// classes below should be later migrated to the JDK 1.2 list.

abstract class OP {
  protected OP next=null;
  protected OP prev=null;
  
  abstract void compile(ExpressionImage ei);
  
  boolean optimize(OPlist list) throws Throwable {return false;};
  
  public OP next() { return next;};
};

class OPlist extends OP {
  
  protected int size=0;
 
  int size() {return size;}; 
  
  OP getFirst() {
    if (Debug.enabled)
      Debug.assert(size>0,"First in empty list.");
    return next;
  };

  OP getLast()  {
    if (Debug.enabled)
      Debug.assert(size>0,"Last in empty list.");
    return prev;
  };

  void addFirst(OP o) {
    o.prev=null; o.next=null;
    if (next!=null) { o.next=next; next.prev=o; };
    next=o;
    size++;
  };
  
  void addLast(OP o) {
    o.prev=null; o.next=null;
    if (prev!=null) { o.prev=prev; prev.next=o; };
    prev=o;
    if (next==null) next=o;  // if first
    size++;
  };
  
  void addBefore(OP e, OP ne) {
    if (e==next) {
      addFirst(ne);
      return;
    };
    ne.prev=e.prev;
    ne.next=e;
    e.prev.next=ne;
    e.prev=ne;
    size++;
  };
  
  void remove(OP o) {
    if (o.prev!=null) o.prev.next=o.next; else this.next=o.next;
    if (o.next!=null) o.next.prev=o.prev; else this.prev=o.prev;
    o.next=null; o.prev=null; // help GC
    size--;
  };

  OPlist cut_end(OP from) {
    OP curr=from;
    OPlist newlist=new OPlist();
    while (curr!=null) {
      OP ncurr=curr.next;
      remove(curr);
      newlist.addLast(curr);
      curr=ncurr;
    };
    return newlist;
  };
  
  // This compiles whole expression
  void compile(ExpressionImage ei) {
    OP curr=next;
    while(curr!=null) {
      curr.compile(ei);
      curr=curr.next;
    };
  };

  boolean optimize() {
    try {
      OP curr=next;
      boolean didsome=false;
      
      while(curr!=null) {
	OP ncurr=curr.next;
	didsome = curr.optimize(this) || didsome;
	curr=ncurr;
      };
      return didsome;
    } catch (Throwable exc) {
      return false;
    };
  };

  public String toString() {
    OP curr=next;
    StringBuffer res=new StringBuffer();
    while (curr!=null) {
      res.append(curr.toString());
      curr=curr.next;
    };
    return res.toString();
  };
  
  
};

class OP_start extends OP {  // Analogous to opening bracket in Lisp
  protected OP_function f=null;
  protected Method m=null;
  protected int id=999;
  
  OP_start(OP_function f) {this.f=f;};
  
  OP_function getFunction() {return f;};
  
  // Called by corresponding OP_function, when method to call is determined.
  // For binary primitive operations this is never called, thus no code
  // will be generated
  void setMethod(Method m,int id) {this.m=m; this.id=id;};
  
  void compile(ExpressionImage ei) {
    if (m !=null) 
      ei.asm_func_start(m,id);
    if (f instanceof OP_logical_not)
      ei.asm_logical_block();
  };

  public String toString() {
    return "[";
  };
  
};

// This class is a placeholder to insert the reference to the object, imple
// memnting the function. As function overloading is allowed the actual
// reference is unknown before ALL formal arguments are parsed.
class OP_param extends OP_start { // Analogous to the list delimiter character
  //  OP_function f=null;
  Class typeInStack=null;
  boolean setbinaryparam=true;
  
  // f is the reference to the OP_function, which is the "closing bracket".
  OP_param(OP_function f,Class typeInStack) {
    super(f);this.typeInStack=typeInStack;
  };

  // Called, when the type of parameter is determined to make param conversion.
  void setConvert(OPlist list, Class cls) {
    if (cls!=typeInStack) {
      if (prev instanceof OP_convert) {
	((OP_convert) prev).setType(cls);
      } else {
	list.addBefore(this,new OP_convert(cls)); // Add conversion OP
      };
      typeInStack=cls;
    };
  };
  
  void compile(ExpressionImage ei) {
    if (m!=null)
      ei.asm_func_param();
    else {
      if (setbinaryparam) {
	if (f instanceof OP_binary) {
	  if (((OP_binary)f).logical)
	    ei.asm_logical_binary_param(((OP_binary)f).opc);
	  else 
	    ei.asm_binary_param(((OP_binary)f).opc);
	};
      };
    };
  };

  public String toString() {
    if (setbinaryparam)
      return ",";
    else
      return ".";
  };
 
};


class OP_load extends OP {
  Class type;
  Object what;
  
  static final char[] typecodes = {'Z','B','C','S','I','J','F','D'};

  OP_load(Class type,Object what) {
    this.type=type;
    this.what=what;
  };
  
  void compile(ExpressionImage ei) {
    if ((type==null) || (what==null)) {
      ei.asm_load_object(null);
      return;
    };
    if (type.isPrimitive())
      ei.asm_load_primitive(what);
    else 
      ei.asm_load_object(what);
    return;
  }; 

  public String toString() {

    if (type.isPrimitive())
      return what.toString()+typecodes[ExpressionImage.primitiveID(type)];
    
    if (what instanceof String)
      return "\""+what+"\"";
    
    return what.toString();
  };

  
};

abstract class OP_function extends OP {

  public boolean optimize(OPlist list) throws Throwable {
    boolean can_interpret=canInterpret();
    if (can_interpret) interpret(list);
    return can_interpret;
  };
  
  abstract boolean canInterpret();
  
  abstract void interpret(OPlist list) throws Throwable;
  
  void consume(OPlist list,OP_load replace_with, int nop,boolean match_start) {
    int toremove=nop*2+1; // nop[load param ] this
    if (match_start) toremove++; // start ?
    
    OP next_op=next;

    OP cop=this;
    for(int i=0; i<toremove; i++) {
      OP prv=cop.prev; list.remove(cop); cop=prv;
    };
    if (next_op!=null) 
      list.addBefore(next_op, replace_with);
    else
      list.addLast(replace_with);
  };
  
  boolean operandsReady(int nop,boolean match_start) {
    boolean ready=true;
    OP oper=prev;
    for(int i=0;(i<nop) && ready;i++) {
      ready= ((oper instanceof OP_param) && (oper.prev instanceof OP_load));
      oper=oper.prev.prev;
    };
    if (match_start)
      ready = ready && (oper instanceof OP_start);
    return ready;
  };
  
  static boolean isFloat(int clsID) {
    return (clsID>5);
  };

  static Number widen(Object o, int clsID) {
    switch (clsID) {
    case 0: // Z
      if (((Boolean)o).booleanValue()) 
	return new Long(1L); 
      else 
	return new Long(0L);
    case 1: // B
      return (Number)o;
    case 2: // C
      return new Long((long)((Character)o).charValue());
    case 3: // S
    case 4: // I
    case 5: // J
    case 6: // F
    case 7: // D
      return (Number)o;
    default:
      if (Debug.enabled)
	Debug.println("Attempt to widen wrong primitive ("+clsID+").");
      return new Long(0L);
    };
    
  };

  static Object narrow(Number val, int clsID) {
    switch (clsID) {
    case 0: // Z
      if (val.longValue()!=0) return Boolean.TRUE; else return Boolean.FALSE;
    case 1: // B
      return new Byte(val.byteValue());
    case 2: // C
      return new Character((char)val.longValue());
    case 3: // S
      return new Short(val.shortValue());
    case 4: // I
      return new Integer(val.intValue());
    case 5: // J
      return new Long(val.longValue());
    case 6:
      return new Float(val.floatValue());
    case 7:
      return new Double(val.doubleValue());
    default:
      if (Debug.enabled)
	Debug.println("Attempt to narrow wrong primitive ("+clsID+").");
      return null;
    };
  };
};


class OP_conditional extends OP_function {
  OPlist trueList;
  OPlist falseList;
  
  OP_conditional() {};
  
  public void setTrueList(OPlist trueList) {
    this.trueList=trueList;
  };
  
  public void setFalseList(OPlist falseList) {
    this.falseList=falseList;
  };

  // should be called after both lists are set
  // Ensures that types of both branches are the same
  public void setType(Class type) {
    
    OP tll=trueList.getLast();
    if (tll instanceof OP_convert) 
      ((OP_convert)tll).setType(type);
    else {
      trueList.addLast(new OP_convert(type));
    };

    OP fll=falseList.getLast();
    if (fll instanceof OP_convert) 
      ((OP_convert)fll).setType(type);
    else {
      falseList.addLast(new OP_convert(type));
    };
    
  };
  
  boolean canInterpret() {
    return false;
  };
  
  void interpret(OPlist list) throws Throwable {
  };
  
  void compile(ExpressionImage ei) {
    ei.asm_branch_start_true();
    trueList.compile(ei);
    ei.asm_branch_start_false();
    falseList.compile(ei);
    ei.asm_branch_end();
  };

  public boolean optimize(OPlist list) throws Throwable {
    boolean optimized;
    if (prev instanceof OP_load) {
      boolean val=((Boolean)((OP_load)prev).what).booleanValue();
      list.remove(prev); // get rid of used value

      optimized=true;
      OPlist integratelist;
      if (val)
	integratelist=trueList;
      else
	integratelist=falseList;
      
      integratelist.optimize();
      OP iop=integratelist.getFirst();
      // Integrate result into the main code
      while (iop!=null) {
	OP niop=iop.next;
	integratelist.remove(iop);
	list.addBefore(this,iop);
	iop=niop;
      };
      list.remove(this);
    } else {
      optimized=trueList.optimize();
      optimized=falseList.optimize() || optimized;
    };
    return optimized;
  };

  public String toString() {
    return "[?"+trueList.toString()+":"+falseList.toString()+"]";
  };
};

class OP_logical_not extends OP_function {
  boolean canInterpret() {
    return (prev instanceof OP_load) && (prev.prev instanceof OP_start);
  };

  void interpret(OPlist list) throws Throwable {
    OP_load l=(OP_load) prev;
    
    // perform the complement
    if (((Boolean)l.what).booleanValue()) 
      l.what=Boolean.FALSE;
    else 
      l.what=Boolean.TRUE;
    
    list.remove(prev.prev); // OP_start
    list.remove(this);
  };
  
  void compile(ExpressionImage ei) {
    ei.asm_logical_unblock_not();
  };

  public String toString() {
    return "!]";
  };
 
};

abstract class OP_unary_primitive extends OP_function {

  boolean canInterpret() {
    return (prev instanceof OP_load);
  };

  void interpret(OPlist list) throws Throwable {
    OP_load val=(OP_load) prev;
    doOperation(val);
    list.remove(this);
  };
  
  // Should modify OP_load to represent correct value
  abstract void doOperation(OP_load val) throws Throwable;
     
};

class OP_convert extends OP_unary_primitive {
  
  Class to;
  
  OP_convert(Class to) {
    this.to=to;
  };
  
  void setType(Class new_to) {to=new_to;};
  
  void doOperation(OP_load c) throws Throwable {
    if (c.type==to) return ;   // it's already the same
    if (((c.type==null) || (c.what==null)) && (!to.isPrimitive()) ) 
      return;  // null has any object type
    
    boolean pc=c.type.isPrimitive();
    boolean pt=to.isPrimitive();
    if (!pc && !pt) {  // object to object
      Debug.assert(to.isAssignableFrom(c.type),"Class types not compatible.");
      return; 
    };
    if (pc && pt) { // Z C B S I L F D
      int pfi=ExpressionImage.primitiveID(c.type);
      int pti=ExpressionImage.primitiveID(to);
      c.what=narrow(widen(c.what,pfi),pti);
      c.type=to;
      return; 
    };
    return; // can't convert between objects and primitives 
  };

  void compile(ExpressionImage ei) {
    ei.asm_convert(to);
  };
  
  public String toString() {
    return "("+to.toString()+")";
  };
  

};

class OP_unary extends OP_unary_primitive {
  int opc;
  
  OP_unary(int opc) {
    this.opc=opc;
  };

  void doOperation(OP_load c) throws Throwable {  
    if (Debug.enabled)
      Debug.assert((c.type!=null) && (c.type.isPrimitive()),
		   "Wrong types for binary OP");
    
    int id=ExpressionImage.primitiveID(c.type);
    
    // Perform unary numeric promotion according to JLS
    Class resClass=ExpressionImage.getUnaryPromoted(c.type);
    if (Debug.enabled)
      Debug.assert(resClass!=null,"Wrong type of operand for unary"+
		   " operation ("+opc+") can't perform unary numeric "+
		   "promotion.");
    int resid=ExpressionImage.primitiveID(resClass);
    
    Number val=widen(c.what,id);
    switch(opc) {
    case 0:
      if (isFloat(id)) 
	val=new Double(-val.doubleValue());
      else
	val=new Long(-val.longValue());
      break;
    case 1:
      val=new Long(~val.longValue());
      break;
    default:
      if (Debug.enabled)
	Debug.println("Wrong unary opcode.");
    };

    c.what=narrow(val,resid);
    c.type=resClass;
  };
  
  void compile(ExpressionImage ei) {
    ei.asm_unary(opc);
  };

  public String toString() {
    return ExpressionImage.unarySymbols[opc];
  };
  
};

class OP_binary extends OP_function {
  int opc=999;
  boolean logical=false;
  OP_binary() {
  };
  
  void setOperation(OPlist list, int opc,boolean logical,Class type) {
    this.logical=logical;
    this.opc=opc;
    if (type!=Optimizer.string_class) { // For strings no conversions is needed
      int cpar=2;
      OP cop=prev;
      while ((cop!=null) && (cpar>=0)) {
	if (cop instanceof OP_param) {
	  OP_param copp=(OP_param)cop;
	  if (copp.getFunction()==this) {
	    copp.setConvert(list,type);
	  };
	};
	cop=cop.prev;
      };
    };
  };

  boolean canInterpret() { return operandsReady(2,false); };
  
  void interpret(OPlist list) throws Throwable {
    if (logical) {
      OP_load c2=(OP_load) prev.prev;
      OP_load c1=(OP_load) prev.prev.prev.prev;
      boolean b1=((Boolean)c1.what).booleanValue();
      boolean b2=((Boolean)c2.what).booleanValue();
      
      switch(opc) {
      case 0 : b1=b1&b2; break;
      case 1 : b1=b1|b2; break;
      default:
	if (Debug.enabled)
	  Debug.println("Wrong logical bitwise operation ("+opc+").");      
      };
     
      if (b1) c1.what=Boolean.TRUE; else c1.what=Boolean.FALSE;
      
      consume(list,c1,2,false);      
      return;
    };
    
    OP_load c2=(OP_load) prev.prev;
    OP_load c1=(OP_load) prev.prev.prev.prev;
    
    if ((opc==ExpressionImage.BI_PL) && (c1.type==Optimizer.string_class)) {
      // String concatenation
      c1.what=((String)c1.what)+String.valueOf(c2.what);
    } else {
      // Operators on primitive types
      if (Debug.enabled)
	Debug.assert((c1.type!=null) && (c1.type==c2.type) && 
		     (c2.type.isPrimitive()),"Wrong types for binary OP");
      
      int id=ExpressionImage.primitiveID(c1.type);
      
      // Widen
      Number n1=widen(c1.what,id),n2=widen(c2.what,id);
      
      // Perform      
      boolean boolres=false;
      boolean resbool=false;
      if (isFloat(id)) {
	double d1=n1.doubleValue(),d2=n2.doubleValue();
	boolean wrop=false;
	switch (opc) {
	case 0 : d1=d1+d2; break; //PL
	case 1 : d1=d1-d2; break; //MI
	case 2 : d1=d1*d2; break; //MU
	case 3 : d1=d1/d2; break; //DI
	case 4 : d1=d1%d2; break; //RE
	case 5 : wrop=true;break; //AN
	case 6 : wrop=true;break; //OR
	case 7 : wrop=true;break; //XO
	case 8 : boolres=true; resbool=(d1==d2); break; //EQ
	case 9 : boolres=true; resbool=(d1!=d2); break; //NE
	case 10: boolres=true; resbool=(d1<d2);  break; //LT
	case 11: boolres=true; resbool=(d1>=d2); break; //GE
	case 12: boolres=true; resbool=(d1>d2);  break; //GT
	case 13: boolres=true; resbool=(d1<=d2); break; //LE
	default :
	  wrop=true;
	};
	if (Debug.enabled && wrop)
	  Debug.println("Wrong operation on float ("+opc+").");      
	
	if (!boolres) n1=new Double(d1);
	else { // booleans are represented by longs temporarily
	  if (resbool) n1=new Long(1L); else n1=new Long(0);
	};
      } else { 
	long l1=n1.longValue(),l2=n2.longValue();
	switch (opc) {
	case 0: l1=l1+l2;break; //PL
	case 1: l1=l1-l2;break; //MI
	case 2: l1=l1*l2; break; //MU
	case 3: l1=l1/l2; break; //DI
	case 4: l1=l1%l2; break; //RE
	case 5: l1=l1&l2; break; //AN
	case 6: l1=l1|l2; break; //OR
	case 7: l1=l1^l2; break; //XO
	case 8 : boolres=true; l1=(l1==l2?1L:0L); break; //EQ
	case 9 : boolres=true; l1=(l1!=l2?1L:0L); break; //NE
	case 10: boolres=true; l1=(l1< l2?1L:0L); break; //LT
	case 11: boolres=true; l1=(l1>=l2?1L:0L); break; //GE
	case 12: boolres=true; l1=(l1> l2?1L:0L); break; //GT
	case 13: boolres=true; l1=(l1<=l2?1L:0L); break; //LE
	case 14: l1=l1<<l2; break;  // LS
	case 15: l1=l1>>l2; break;  // RS
	case 16: { // for this kind of shifts the bit width of variable is
	  // important
	  if (id==4) // == because there is unary numeric promotion before op
	    l1=(int)l1>>>l2; 
	  else 
	    l1=l1>>>l2; 
	  break; 
	}; // RUS
	default :
	  if (Debug.enabled)
	    Debug.println("Wrong operation on integer ("+opc+").");      
	};
	n1=new Long(l1);
      };
      
      // Narrow    
      if (boolres) {
	c1.what=narrow(n1,0);
	c1.type=Boolean.TYPE;
      } else c1.what=narrow(n1,id);
    };
    consume(list,c1,2,false);
  };

  void compile(ExpressionImage ei) {
    if (logical)
      ei.asm_logical_binary(opc);
    else
      ei.asm_binary(opc);
  };

  public String toString() {
    if (logical)
      return ExpressionImage.logicalSymbols[opc];
    else
      return ExpressionImage.binarySymbols[opc];
  };

  
};



class OP_call extends OP_function {
  
  Method m=null;
  int nargs=999;
  Class[] args=null;
  boolean interpretable=false;

  OP_call() {};

  boolean canInterpret() { 
    return (interpretable && operandsReady(nargs,true)); 
  };
  
  void setMethod(OPlist list,Method m, int id,boolean interpretable) {
    args=m.getParameterTypes();
    nargs=args.length;
    this.interpretable=interpretable;
    this.m=m;
    // Search for parameters and set their types
    int cpar=args.length-1;
    OP cop=prev;
    while ((cop!=null) && (cpar>=0)) {
      if (cop instanceof OP_param) {
	OP_param copp=(OP_param)cop;
	if (copp.getFunction()==this) {
	  copp.setConvert(list,args[cpar--]);
	  copp.setMethod(m,id);
	};
      };
      cop=cop.prev;
    };
    if (Debug.enabled) 
      Debug.assert((cop!=null) && (cpar==-1),
		  "Not all parameters are present at function call");
    
    OP_start ops=null;
    while ((cop!=null) && (ops==null)) {
      if (cop instanceof OP_start) {
	ops=(OP_start)cop;
	if (ops.getFunction()!=this) ops=null;
      };
      cop=cop.prev;
    };
    if (Debug.enabled) 
      Debug.assert(ops!=null,"OP_start was not found.");
    ops.setMethod(m,id);
  };
  
  void interpret(OPlist list) throws Throwable {
    Object[] args=new Object[nargs];
    
    // collect arguments
    OP cop=prev;
    int argc=nargs-1;
    while (argc>=0) {
      args[argc--]=((OP_load)cop.prev).what;
      cop=cop.prev.prev;
    };
    if (Debug.enabled) 
      Debug.assert(cop instanceof OP_start,"Wrongly formed function call.");
    
    Object val=m.invoke(null,args);
    OP_load result=new OP_load(m.getReturnType(),val);
    
    consume(list,result,nargs,true);
    
  };
  
  void compile(ExpressionImage ei) {
    ei.asm_func_call();
  };

  public String toString() {
    return m.getName()+"]";
  };
}






