/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: OPbinary.java 490 2006-10-01 16:08:04Z metlov $
 *
 * This file is part of the Java Expressions Library (JEL).
 *   For more information about JEL visit :
 *    http://kinetic.ac.donetsk.ua/JEL/
 *
 * (c) 1998 -- 2006 by Konstantin Metlov(metlov@kinetic.ac.donetsk.ua);
 *
 * 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 java.util.Stack;

/**
 * A tree node, representing binary operation.
 */
public class OPbinary extends OP {

  /** code of this operation */
  public int code;

  /** index into ops array to get code for this op. */
  private int opsIDX; 

  // Names of binary operations by ID in the readable form.
  private final static String[] opNames;

  /** binary promotions of base types */
  protected final static byte[][] promotions;

  // code chunks to implement the ops
  private final static  int[][] ops;
  
  // type of operands promotion by the opcode
  private static final byte[] promotionTypes;

  static {
    promotions=(byte[][])TableKeeper.getTable("promotions");
    ops=(int[][])TableKeeper.getTable("ops");
    promotionTypes=(byte[])TableKeeper.getTable("promotionTypes");
    opNames=(String[])TableKeeper.getTable("binOpNames");
  };

  /**
   * 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
   * 20  --  reserved (used internally for string concatenation)
   * </PRE>
   * @param paramOPs stack holding the operands
   * @param opcode is the operation code
   */
  public OPbinary(Stack<OP> paramOPs, int opcode) throws CompilationException {
    if (Debug.enabled)
      Debug.check((opcode>=0) && (opcode<opNames.length));

    // get the operands types
    chi=new OP[2];
    chi[1]=(OP)paramOPs.pop();
    chi[0]=(OP)paramOPs.pop();

    int op2ID=chi[1].resID;
    Class op2Type=chi[1].resType;
    int op1ID=chi[0].resID;
    Class op1Type=chi[0].resType;

    // separate out string concatenation
    if (((op1ID==10) || (unwrapType[op1ID]==11)) && (opcode==0))
      opcode=20;

    this.code=opcode;
    //    Debug.println("opcode="+opcode+" op1ID="+op1ID+" op2ID="+op2ID);

    // perform the promotion of operands and determine the index
    // variables opsIDX, resID, optionally resType (if resID=8) and 
    // the following will be defined
    int op2cvtID=op2ID,op1cvtID=op1ID;
    
    resID=-1;
    boolean second_narrowing=false;
    int op1IDuwrp=unwrapType[op1ID];
    int op2IDuwrp=unwrapType[op2ID];

    switch (promotionTypes[opcode]) {
    case 0: // binary promotion with boolean result
      resID=0;
    case 1: // binary promotion
      op1cvtID=op2cvtID=opsIDX=
        promotions[op1IDuwrp][op2IDuwrp];
      if (resID<0) resID=opsIDX;

      if (opsIDX==-1) { // types are incompatible (can't promote)
        Object[] paramsExc={op1Type,op2Type,opNames[opcode]};
        throw new CompilationException(15,paramsExc);
      };

      break;
    case 3: // array promotion
      resType=(op1Type!=null?op1Type.getComponentType():null);
      if (resType==null)
        throw new CompilationException(18,null);
      resID=typeID(resType);
      opsIDX=(resID>=8?8:resID);
    case 2: // unary promotion of the first operand, second to int
      if (resID<0) {
        resID=op1cvtID=opsIDX=
          OPunary.unary_prmtns[op1IDuwrp];
        second_narrowing=true;
      };

      // integral types mask
      //      7 6 5 4 3 2 1 0
      //      0 0 1 1 1 1 1 0 = 0x3E
      if (((0x3E>>op2IDuwrp) & 1)==0) { // type is not integral
        Object[] paramsExc={opNames[opcode],op2Type};
        throw new CompilationException(27,paramsExc);
      };
      op2cvtID=4;
      break;
    case 4: // string concatenation promotion
      opsIDX=(op2ID>11?8:op2ID);
      op1cvtID=10; // TSB
      resID=10;  // TSB
      break;
    default:
      if (Debug.enabled)
        Debug.println("Wrong promotion type for binary OP "+
                      promotionTypes[opcode]);
    };
    
    // check if the OP can be implemented
    if (ops[opcode][opsIDX]==0xFF) { // operation is not defined on types
      Object[] paramsExc={opNames[opcode],op1Type,op2Type};
      throw new CompilationException(16,paramsExc);
    };
    
    // insert type conversion opcode
    if ((op1ID!=op1cvtID) && (op1cvtID!=8)) {
      paramOPs.push(chi[0]);
      chi[0]=new OPunary(paramOPs,op1cvtID,null,op1cvtID==10);
      //                                        can narrow to TSB
    };

    if ((op2ID!=op2cvtID) && (op2cvtID!=8)) {
      paramOPs.push(chi[1]);
      chi[1]=new OPunary(paramOPs,op2cvtID,null,second_narrowing);
    };

    if (resID!=8)
      resType=specialTypes[resID];
  };
 
  public void compile(ClassFile cf) {
    if ((code==17) || (code==18)) {
      chi[0].compile(cf);
      cf.code(code-(17 - 0xF4)); // logical_param AND/OR
      chi[1].compile(cf);
      cf.code(code-(17 - 0xF6)); // logical_end AND/OR
    } else {
      chi[0].compile(cf);
      cf.code(0xFA); //   ensure value;
      chi[1].compile(cf);
      cf.code(0xFA); //   ensure value;

      cf.code(ops[code][opsIDX] & 0xFFFFFFFFL); 
      // & is needed to prevent sign extension when converting "int" ops
      // element into "long" argument of code.
      
      cf.noteStk(chi[0].resID,-1);
      cf.noteStk(chi[1].resID,-1);
      
      if (cf.currJump==0) // jumps do not load anything to the stack
        cf.noteStk(-1,resID);
    };
  };

  public Object eval() throws Exception {
    
    Object c1w=null;
    boolean ep1=false;
    int c1ID=chi[0].resID;
    Object c2w=null;
    boolean ep2=false;
    int c2ID=chi[1].resID;
    
    try {
      c1w=chi[0].eval();
    } catch (Exception e) {
      ep1=true;
    };
    
    try {
      c2w=chi[1].eval();
    } catch (Exception e) {
      ep2=true;
    };

    try {
      if (ep1 || ep2 || (code==19)) // array access can't be evaluated.
        throw new Exception();
      
      if (code==20) {
        ((StringBuffer)c1w).append(String.valueOf(c2w));
      } else if ((c1ID>=8) || (c2ID>=8)) {
        // the only way the objects appear in this context
        // are the comparisons
        if (Debug.enabled) {
          Debug.check((code>=8)||(code<=13),
                       "only comparisons and concatenation binops can "+
                       "operate on objects.");
          Debug.check(c1ID!=10, "No TSB in this context 1");
          Debug.check(c1ID!=10, "No TSB in this context 2");
        };

        // only string literal comparisons are interpreted
        if ((c1ID!=11) || (c2ID!=11))
          throw new Exception();

        // compare the strings
        int res=CompiledExpression.compare((String)c1w,(String)c2w);
        c1w=((0x3172A>>>(3*(code-8)))&((res>0)?1:((res<0)?4:2)))>0
          ?Boolean.TRUE:Boolean.FALSE;
      } else {  // binary on primitive types
        // Widen
        Number n1=widen(c1w,c1ID);
        Number n2=widen(c2w,c2ID);
      
        // 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) {
          c1w=narrow(n1,0);
        } else c1w=narrow(n1,resID);
        
      };
      return c1w;
    } catch (Exception thr) {
      // if can't evaluate -- replace operands at least

      if (!ep1)
        chi[0]=new OPload(chi[0],c1w);
      
      if (!ep2)
        chi[1]=new OPload(chi[1],c2w);

      throw thr;
      //      Debug.reportThrowable(thr);
      // IGNORE
    }
  };

};

