/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: OPbinary.java,v 1.4 2000/09/09 19:51:48 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.

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

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

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

  // code chunks to implement the ops
  private final static  int[][] ops;
  
  // open jumps left after the ops
  private static final char[][] openjumps;

  // type of operands promotion by the opcode
  private static final byte[] promotionTypes;

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

  /**
   * 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 sctring concatenation)
   * </PRE>
   * @param typesStk holds current items on stack
   * @param opcode is the operation code
   */
  public OPbinary(TypesStack typesStk, Stack paramOPs, int opcode,OPlist list) 
    throws CompilationException {
    if (Debug.enabled)
      Debug.assert((code>=0) && (code<opNames.length) && 
                   (opNames.length==opSymbols.length));

    // get the operands types
    int op2ID=typesStk.peekID();
    Class op2Type=typesStk.pop();
    int op1ID=typesStk.peekID();
    Class op1Type=typesStk.pop();
    OP p2CvtPlaceholder=(OP)paramOPs.pop();
    OP p1CvtPlaceholder=(OP)paramOPs.pop();

    // separate out the string concatenation
    if (((op1ID==10) || (op1ID==11)) && (code==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;
    switch (promotionTypes[opcode]) {
    case 0: // binary promotion with boolean result
      resID=0;
    case 1: // binary promotion
      op1cvtID=op2cvtID=opsIDX=
        promotions[TypesStack.baseType[TypesStack.unwrapType[op1ID]]]
        [TypesStack.baseType[TypesStack.unwrapType[op2ID]]];
      if (resID<0) resID=opsIDX;

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

      break;
    case 2: // unary promotion of the first operand, second to int
      resID=op1cvtID=opsIDX=
        OPunary.unary_prmtns[TypesStack.unwrapType[op1ID]];
      if (!TypesStack.isIntegral(TypesStack.unwrapType[op2ID])) {
        Object[] paramsExc={opNames[opcode],op2Type};
        throw new CompilationException(-1,27,paramsExc);
      };
      op2cvtID=4;
      second_narrowing=true;
      break;
    case 3: // array promotion
      resType=(op1Type!=null?op1Type.getComponentType():null);
      if (resType==null)
        throw new CompilationException(-1,18,null);
      resID=TypesStack.typeID(resType);

      if (!TypesStack.isIntegral(TypesStack.unwrapType[op2ID])) {
      Object[] paramsExc={opNames[opcode],op2Type};
        throw new CompilationException(-1,27,paramsExc);
      };

      op2cvtID=4;
      opsIDX=TypesStack.baseType[resID];
      break;
    case 4: // string concatenation promotion
      if ((op1ID!=10) && (op1ID!=11)) //STRCAT defined on TSBs or Strings 
        throw new CompilationException(-1,17,null);
      opsIDX=TypesStack.baseType[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(-1,16,paramsExc);
    };
    
    // insert type conversion opcode
    if (op1ID!=op1cvtID) {
      typesStk.pushID(op1ID);
      list.addAfter(p1CvtPlaceholder,
                    new OPunary(typesStk,op1cvtID,null,false));
      typesStk.pop();
    };
    if (op2ID!=op2cvtID) {
      typesStk.pushID(op2ID);
      list.addAfter(p2CvtPlaceholder,
                    new OPunary(typesStk,op2cvtID,null,second_narrowing));
      typesStk.pop();
    };
    
    typesStk.pushID(resID,resType); // push the result type on stack
  };
 
  /**
   * Returns number of parameters for this function.
   */
  public int getNParams() {
    return 2;
  };
  
  protected void compile_par(ClassFile cf, int n) {
    if ((code==17) || (code==18)) {
      // logical AND/OR
      if (n==1) 
        cf.logical_param(code==17);
      else
        cf.logical_end(code==17);
    } else cf.ensure_value();
  };

  protected void compile(ClassFile cf) {
    if ((code!=17) && (code!=18)) {
      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 (code==20) {
        ((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];
  };

};





