/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: OPbinary.java,v 1.1 2000/02/11 22:08:18 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 -- 2000 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 gnu.jel.debug.Debug;
import gnu.jel.reflect.Method;
import java.util.Stack;

public class OPbinary extends OPfunction {
  private int code;

  private int opsIDX; // index into ops array to get code for this op.
  private boolean strcat=false;

  private static Method[] sb_append;
  private static Method sb_append_string;
  private static Class sb_class;
  private static Class string_class;

  private int paramcnt=0;
  
  static {
    try {
      sb_class=Class.forName("java.lang.StringBuffer");
      string_class=Class.forName("java.lang.String");

      Class[] param=new Class[1];
      param[0]=string_class;
      sb_append_string=new Method(sb_class.getMethod("append",param));
      
      sb_append=new Method[9]; // up to L
      param[0]=Class.forName("java.lang.Object");
      sb_append[8]=new Method(sb_class.getMethod("append",param));
 
      // -2 below excludes refs and voids
      for(int i=0;i<8;i++) { // Z B C S I J F D
        // byte is int, short is int
        param[0]=TypesStack.primitiveTypes[i==1?4:i==3?4:i];
        sb_append[i]=new Method(sb_class.getMethod("append",param));
      };
    } catch (Exception exc) {
      if (Debug.enabled)
        Debug.reportThrowable(exc);
    };
  };
  
  /**
   * Names of binary operations by ID in the readable form.
   */
  private final static String[] opNames={"add","substract","multiply",
                                         "divide","remainder",
                                         "bitwise and","bitwise or",
                                         "bitwise xor","equal","not equal",
                                         "less","greater or equal",
                                         "greater","less or equal",
                                         "left shift", "signed right shift",
                                         "unsigned right shift",
                                         "logical and","logical or",
                                         "array element access"};
  /**
   * Symbols of binary operations by ID in the readable form.
   */
  private final static String[] opSymbols={
    "+","-","*","/","%","&","|","^","==","!=","<",">=",
    ">","<=","<<",">>",">>>","&&","||","{}"};

  protected final static
    int[][] promotions={
      //Z   B   C   S   I   J   F   D 
      {000,255,255,255,255,255,255,255}, // Z
      {255,004,004,004,004,005,006,007}, // B
      {255,004,004,004,004,005,006,007}, // C
      {255,004,004,004,004,005,006,007}, // S
      {255,004,004,004,004,005,006,007}, // I
      {255,005,005,005,005,005,006,007}, // J
      {255,006,006,006,006,006,006,007}, // F
      {255,007,007,007,007,007,007,007}};// D
  
  private final 
    static  int[][] ops={
      // First opcode (255 if op is not allowed)
      // Z   B   C   S   I   J   F   D  REF
      {255, 96,255, 96, 96, 97, 98, 99,255},  // PL
      {255,100,255,100,100,101,102,103,255},  // MI
      {255,104,255,104,104,105,106,107,255},  // MU
      {255,108,255,108,108,109,110,111,255},  // DI
      {255,112,255,112,112,113,114,115,255},  // RE
      {126,126,255,126,126,127,255,255,255},  // AN
      {128,128,255,128,128,129,255,255,255},  // OR
      {130,130,255,130,130,131,255,255,255},  // XO
      {  0,  0,  0,  0,  0,148,150,152,255},  // EQ
      {  0,  0,  0,  0,  0,148,150,152,255},  // NE
      {  0,  0,  0,  0,  0,148,150,152,255},  // LT
      {  0,  0,  0,  0,  0,148,149,151,255},  // GE
      {  0,  0,  0,  0,  0,148,149,151,255},  // GT
      {  0,  0,  0,  0,  0,148,150,152,255},  // LE
      {120,120,120,120,120,121,255,255,255},  // LS
      {122,122,122,122,122,123,255,255,255},  // RSS
      {124,124,124,124,124,125,255,255,255},  // RUS
      {  0,255,255,255,255,255,255,255,255},  // LAN
      {  0,255,255,255,255,255,255,255,255},  // LOR
      { 51, 51, 52, 53, 46, 47, 48, 49, 50}   // ARR
    };
  
  // tells if the binary operation generated unclosed jump,
  // unclosed jump also means that both operands are removed from stack,
  // no result is loaded
  private static final int[][] openjumps={
    // Z   B   C   S   I   J   F   D  REF 
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // PL
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // MI
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // MU
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // DI
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // RE
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // AN
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // OR
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // XO
    {159,159,159,159,159,153,153,153,  0},  // EQ
    {160,160,160,160,160,154,154,154,  0},  // NE
    {161,161,161,161,161,155,155,155,  0},  // LT
    {162,162,162,162,162,156,156,156,  0},  // GE
    {163,163,163,163,163,157,157,157,  0},  // GT
    {164,164,164,164,164,158,158,158,  0},	// LE
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // LS
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // RSS
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // RUS
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // LAN
    {  0,  0,  0,  0,  0,  0,  0,  0,  0},  // LOR
    {  0,  0,  0,  0,  0,  0,  0,  0,  0}   // ARR
  };

  
  /**
   * Constructs a new binary operation.
   * <P>Codes are following:
   * <PRE>
   * 0   --  addition
   * 1   --  substraction
   * 2   --  multiplication
   * 3   --  division
   * 4   --  remainder
   * 5   --  bitwise AND
   * 6   --  bitwise OR
   * 7   --  bitwise and logical XOR
   * 8   --  comparizon for equality
   * 9   --  comparizon for non-equality
   * 10  --  comparizon for "less" <
   * 11  --  comparizon for "greater or equal" >=
   * 12  --  comparizon for "greater" >
   * 13  --  comparizon for "less or equal" <=
   * 14  --  bitwise left shift <<
   * 15  --  bitwise right signed shift >>
   * 16  --  bitwise right unsigned shift >>>
   * 17  --  logical conjunction operator (AND)
   * 18  --  logical disjunction operator (OR)
   * 19  --  array element access operation
   * </PRE>
   * @param typesStk holds current items on stack
   * @param code is the operation code
   */
  public OPbinary(TypesStack typesStk, Stack paramOPs, int code,OPlist list) 
    throws IllegalStateException {
    if (Debug.enabled)
      Debug.assert((code>=0) && (code<opNames.length) && 
                   (opNames.length==opSymbols.length));

    this.code=code;
    int op2ID=typesStk.peekID();
    Class op2Type=typesStk.pop();
    boolean unwrap2OP=false;
    if (op2ID==8) {// try to unwrap second operand
      int unwrapOP2ID=TypesStack.unwrapTypeID(op2Type);
      unwrap2OP=(unwrapOP2ID>0);
      if (unwrap2OP) op2ID=unwrapOP2ID;
    };

    int op1ID=typesStk.peekID();
    Class op1Type=typesStk.pop();
    boolean unwrap1OP=false;
    if (op1ID==8) {// try to unwrap first operand
      int unwrapOP1ID=TypesStack.unwrapTypeID(op1Type);
      unwrap1OP=(unwrapOP1ID>0);
      if (unwrap1OP) op1ID=unwrapOP1ID;
    };

    OP p2CvtPlaceholder=(OP)paramOPs.pop();
    OP p1CvtPlaceholder=(OP)paramOPs.pop();

    if ((op1ID < 8) && (op2ID < 8)) {
      // binary on primitive types
      if ((code<14) || (code>16)) {
        // operation with binary promotion
        opsIDX=promotions[op1ID][op2ID];
        if (opsIDX==255)
          throw new IllegalStateException("Types "+op1Type+" and "+op2Type+
                                          " are incompatible for "+
                                          opNames[code]+" operation.");
        // insert conversion code
        if ((op1ID!=opsIDX) || (unwrap1OP)) {
          typesStk.pushID(op1ID);
          list.addAfter(p1CvtPlaceholder,
                        new OPunary(typesStk,opsIDX,null,false));
          typesStk.pop();
        };
        if ((op2ID!=opsIDX) || (unwrap2OP)) {
          typesStk.pushID(op2ID);
          list.addAfter(p2CvtPlaceholder,
                        new OPunary(typesStk,opsIDX,null,false));
          typesStk.pop();
        };
        
        if (ops[code][opsIDX]==255)
          throw new IllegalStateException("The "+opNames[code]+" operation"+
                                          " is not defined for "+
                                          TypesStack.primitiveTypeNames[resID]+
                                          "s.");
        // determine the result type
        resID=opsIDX;
        // exception for comparizons where result is always boolean
        if ((code>=8) && (code<=13)) resID=0; 
      } else {
        // operation with unary promotion ( shift commands )
        opsIDX=resID=OPunary.unary_prmtns[op1ID];

        // convert the second operand to int
        typesStk.pushID(op2ID);
        list.addAfter(p2CvtPlaceholder,new OPunary(typesStk,4,null,true));
        typesStk.pop();
      };
    } else {
      // either string concatenation or array element access
      if (code==0) {  // string concatenation
        if (op1Type==string_class) {
          // convert to tsb
          typesStk.push(op1Type);
          list.addAfter(p1CvtPlaceholder,
                        new OPunary(typesStk,8,TypesStack.tsb_class,false));
          typesStk.pop();
        } else if (!TypesStack.isTSB(op1Type))
          throw new IllegalStateException("First operand for string "+
                                          "concatenation must be string.");
        opsIDX=8;
        resID=8;
        resType=TypesStack.tsb_class;
        strcat=true;
      } else if (code==19) {  // array element access
        if (!op1Type.isArray())
          throw new IllegalStateException("The first operand of the array "+
                                          "element access operation must "+
                                          "be an array.");
        if (!TypesStack.isWidening(op2ID,4))
          throw new IllegalStateException("Can't automatically convert "+
                                          "array index of type "+op2Type+
                                          " to int.");
        // convert the index to int if needed
        if ((op2ID!=4) || (unwrap2OP)) {
          typesStk.pushID(op2ID);
          list.addAfter(p2CvtPlaceholder,new OPunary(typesStk,4,null,false));
          typesStk.pop();
        };
        resType=op1Type.getComponentType();
        resID=TypesStack.primitiveID(resType);
        opsIDX=resID;
      } else throw new IllegalStateException("Operation \""+opNames[code]+
                                             "\" is defined on primitive "+
                                             "types only.");
    };
    typesStk.pushID(resID,resType);
  };
  
  /**
   * Returns number of parameters for this function.
   */
  public int getNParams() {
    return 2;
  };
  
  protected void compile_pre(ClassFile cf) {
    paramcnt=2;
  };
  
  protected void compile_par(ClassFile cf) {
    paramcnt--;
    if ((code==17) || (code==18)) {
      // logical AND/OR
      if (paramcnt==1) 
        cf.logical_param(code==17);
      else
        cf.logical_end(code==17);
    } else cf.ensure_value();
  };

  protected void compile(ClassFile cf) {
    if (strcat) {
      cf.code(0xb6);                             //| invokevirtual
      if (cf.typesStk.peek()==string_class)
        cf.codeI(cf.getIndex(sb_append_string,10)); //|       .append(String)
      else
        cf.codeI(cf.getIndex(sb_append[cf.typesStk.peekID()],10));//|.append(X)
      cf.typesStk.pop(); // stringbuffer remains
    } else {
      if ((code!=17) && (code!=18)) {
        // binary on primitive types
        cf.code(ops[code][opsIDX]);
        cf.currJump=openjumps[code][opsIDX];
        cf.typesStk.pop();
        cf.typesStk.pop();
        
        if (cf.currJump==0) // jumps do not load anything to the stack
          cf.typesStk.pushID(resID,resType);
      };
    };
  };
  
  /**
   * Attempts to perform this operation.
   * @param list is the list of OPs this one belong to, 
   *             if eval is unsuccessful this list is not modified.
   */
  protected void eval(OPlist list) {
    if (code==19) return; // array access can't be evaluated.
    try {
      OPload c2=(OPload) this.prev;
      OPload c1=(OPload) this.prev.prev;
      if (strcat) {
        ((StringBuffer)c1.what).append(String.valueOf(c2.what));
      } else {  // binary on primitive types
        // Widen
        Number n1=TypesStack.widen(c1.what,c1.resID);
        Number n2=TypesStack.widen(c2.what,c2.resID);
      
        // Perform      
        boolean boolres=false;
        boolean resbool=false;
        if ((opsIDX>=6) && (opsIDX<=7)) {  // operations on floating point
          double d1=n1.doubleValue(),d2=n2.doubleValue();
          boolean wrop=false;
          switch (code) {
          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 ("+code+").");      
        
          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 { // operations on integers
          long l1=n1.longValue(),l2=n2.longValue();
          switch (code) {
          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 17:
          case 5: l1=l1&l2; break; //AN
          case 18:
          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 (resID==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 ("+code+").");      
          };
          n1=new Long(l1);
        };
      
        // Narrow    
        if (boolres) {
          c1.what=TypesStack.narrow(n1,0);
          c1.resType=Boolean.TYPE;
          c1.resID=0;
        } else c1.what=TypesStack.narrow(n1,resID);
        
      };
      list.remove(this.prev); // remove second operand
      list.remove(this);      // remove this OPfunction
    } catch (Throwable thr) {
      //      Debug.reportThrowable(thr);
      // IGNORE
    };
  };

  public String toString() {
    return opSymbols[code];
  };

};





