/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: OPunary.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;

public class OPunary extends OPfunction {
  protected int code; // operation code

  // The full list of codes is :
  // 0  -- negation (applicable to anything except boolean)
  // 1  -- bitwise not (applicable to all integral types)
  // 2  -- logical not (applicable to booleans only)
  // 3  -- convert to boolean
  // 4  -- convert to byte
  // 5  -- convert to char
  // 6  -- convert to short
  // 7  -- convert to int
  // 8  -- convert to long
  // 9  -- convert to float
  // 10 -- convert to double
  // 11 -- convert to object (in this case the cls parameter gives class)
  // 12 -- return

  private boolean str_to_tsb=false;
  private boolean tsb_to_str=false;

  private static Method sb_constructor;
  private static Method sb_toString;
  protected static Class sb_class;
  protected static Class string_class;
  
  static {
    try {
      sb_class=Class.forName("java.lang.StringBuffer");
      string_class=Class.forName("java.lang.String");
      Class[] params=new Class[1];
      params[0]=string_class;
      sb_constructor=new Method(sb_class.getConstructor(params));
      params=new Class[0];
      sb_toString=new Method(sb_class.getMethod("toString",params));
    } catch (Exception e) {
      if (Debug.enabled)
        Debug.reportThrowable(e);
    };
  };

  protected final static
    //                     Z   B   C   S   I   J   F   D   L VOID
    int[] unary_prmtns=  {0 ,  4,  4,  4,  4,  5,  6,  7,  8,  9};

  private final
    static int[][] una ={
      // Z      B      C      S      I        J      F      D   L   VOID  OP/TO
      {0x74,  0x74,  0xFF,  0x74,  0x74,    0x75,  0x76,  0x77,0xFF,0xFF},// NE
      {0xFF,0x8202,0x8202,0x8202,0x8202,0x838502,  0xFF,  0xFF,0xFF,0xFF},// NO
      {   0,  0xFF,  0xFF,  0xFF,  0xFF,    0xFF,  0xFF,  0xFF,0xFF,0xFF},//LNO
      {0x00,  0xFF,  0xFF,  0xFF,  0xFF,    0xFF,  0xFF,  0xFF,0xFF,0xFF},// Z
      {0x91,  0x00,  0x91,  0x91,  0x91,  0x9188,0x918B,0x918E,0XFF,0xFF},// B
      {0x92,  0x92,  0x00,  0x92,  0x92,  0x9288,0x928B,0x928E,0xFF,0xFF},// C
      {0x93,  0x93,  0x93,  0x00,  0x93,  0x9388,0x938B,0x938E,0xFF,0xFF},// S
      {0x00,  0x00,  0x00,  0x00,  0x00,    0x88,  0x8B,  0x8E,0xFF,0xFF},// I
      {0x85,  0x85,  0x85,  0x85,  0x85,    0x00,  0x8C,  0x8F,0xFF,0xFF},// J
      {0x86,  0x86,  0x86,  0x86,  0x86,    0x89,  0x00,  0x90,0xFF,0xFF},// F
      {0x87,  0x87,  0x87,  0x87,  0x87,    0x8A,  0x8D,  0x00,0XFF,0xFF},// D
      {0xFF,  0xFF,  0xFF,  0xFF,  0xFF,    0xFF,  0xFF,  0xFF,0xc0,0xFF},// L
      {0xAC,  0xAC,  0xAC,  0xAC,  0xAC,    0xAD,  0xAE,  0xAF,0xB0,0xB1} //RET
    };

  private final
    static String[] opSymbols={"--","~","!","(Z)","(B)",
                               "(C)","(S)","(I)","(J)",
                               "(F)","(D)","(L)","<RET>"};
  
  private final
    static String[] opNames={"negation","bitwise inversion",
                             "logical inversion","cast to boolean",
                             "cast to byte","cast to char",
                             "cast to short","cast to int",
                             "cast to long","cast to float",
                             "cast to double","cast to reference",
                             "return"};


  static {
    if (Debug.enabled)
      Debug.assert((opNames.length==opSymbols.length) &&
                   (opNames.length==una.length));
  };

  /**
   * Constructs a new unary operation.
   * <P>Codes are following:
   * <PRE>
   * 0  -- negation (applicable to anything except boolean)
   * 1  -- bitwise not (applicable to all integral types)
   * 2  -- logical not (applicable to booleans only)
   * 3  -- return the type in stack
   * </PRE>
   * @param typesStk stack holding the current types (will be updated)
   * @param code operation code
   * @param cls class to convert to (used only if code==11)
   */
  public OPunary(TypesStack typesStk, int code) 
    throws IllegalStateException {
    if (Debug.enabled)
      Debug.assert((code>=0) && (code<=3));

    if (code==3) code=12;
    this.code=code;

    int opTypeID=typesStk.peekID();
    if (una[code][opTypeID]==0xFF)
      throw new IllegalStateException("The '"+opNames[code]+"' operation "+
                                      "is not supported on "+
                                      TypesStack.primitiveTypeNames[opTypeID]+
                                      "s");

    typesStk.pop();    
    if (code!=12) {       // ordinary ops do unary promotion
      resID=unary_prmtns[opTypeID]; 
      typesStk.pushID(resID,null);      
    } else resID=opTypeID;   // return stores the type, destroys the value
  };

  /**
   * Creates conversion operation to the given class.
   * @param typesStk stack holding the current types (will be updated)
   * @param clsID ID of primitive type to convert to.
   * @param cls the class to convert to, in case cldID=8
   * @param allownarrowing if narrowing conversions are allowed.
   */
  public OPunary(TypesStack typesStk, int clsID, Class cls,
                 boolean allownarrowing) {
    if (Debug.enabled)
      Debug.assert((clsID>=0) && (clsID<=8) && ((clsID!=8) || (cls!=null)));

    code=clsID+3;

    resID=clsID;

    Class currentClass=typesStk.peek();    
    if (clsID==8) {
      str_to_tsb=(TypesStack.isString(currentClass) && 
                  TypesStack.isTSB(cls));
      tsb_to_str=(TypesStack.isString(cls) && 
                  TypesStack.isTSB(currentClass));
      resType=cls;
    } else resType=TypesStack.primitiveTypes[resID];

    if (una[code][typesStk.peekID()]==0xFF)
      throw new IllegalStateException("Can not convert "+typesStk.peek()+
                                      " to "+resType+" .");

    if (!allownarrowing && !TypesStack.isWidening(currentClass,resType))
      throw new IllegalStateException("You must specify narrowing conversion "+
                                      "from "+typesStk.peek()+
                                      " to "+resType+" explicitly.");
    
    typesStk.pop();
    typesStk.pushID(resID,resType);
  };

  /**
   * Creates conversion operation to the given class.
   * @param typesStk stack holding the current types (will be updated)
   * @param cls the class to convert to, in case cldID=8
   * @param allownarrowing if narrowing conversions are allowed.
   */
  public OPunary(TypesStack typesStk, Class cls,
                 boolean allownarrowing) {
    this(typesStk,TypesStack.primitiveID(cls),cls,allownarrowing);
  };


  /**
   * Returns number of parameters for this function.
   */
  public int getNParams() {
    return 1;
  };

  protected void compile_pre(ClassFile cf) {
    if (code==2) cf.labels_block();
    if (str_to_tsb) {
      cf.code(0xBB);                            //| new
      cf.codeI(cf.getIndex(sb_class,9));        //    java.lang.StringBuffer
      cf.code(0x59);                            //| dup
      cf.typesStk.pushID(8,sb_class);
      cf.typesStk.pushID(8,sb_class);
    };
  };

  protected void compile(ClassFile cf) {
    if (str_to_tsb) {
      cf.code(0xB7);                            //| invokespecial
      cf.codeI(cf.getIndex(sb_constructor,10));
      cf.typesStk.pop();
      cf.typesStk.pop();
      // the stringbuffer reference remains in stack
    } else if (tsb_to_str) {
      cf.code(0xb6);                            //| invokevirtual
      cf.codeI(cf.getIndex(sb_toString,10));
      cf.typesStk.pop();
      cf.typesStk.pushID(8,string_class);
    } else {
      if (code==2)
        cf.labels_unblock_not();
      else {
        cf.ensure_value();
      
        cf.code(una[code][cf.typesStk.peekID()]);

        // ------- hacks
        if (code==1) // note possible extra item in stack (for BITWISE NOT)
          cf.typesStk.tempExcessWords(TypesStack.stkoccup[resID]);
        else if (code==11) // code CP index for conversion to reference
          cf.codeI(cf.getIndex(resType,9));
        // ------- end hacks
        
        cf.typesStk.pop();
        if (code!=12) 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==12) return; // "return" operation can't be evaluated
    if (! (prev instanceof OPload)) return; // operand is not ready

    OPload opl=(OPload) prev;

    if (code==2) { // logical not
      if (((Boolean)opl.what).booleanValue())
        opl.what=Boolean.FALSE;
      else
        opl.what=Boolean.TRUE;
    } else if (code<2) {
      Number val=TypesStack.widen(opl.what,opl.resID);
      switch(code) {
      case 0:  // negation
        if (opl.resID>5)
          val=new Double(-val.doubleValue());
        else
          val=new Long(-val.longValue());
        break;
      case 1:  // bitwise complement
        val=new Long(~val.longValue());
        break;
      default:
        if (Debug.enabled)
          Debug.assert(code>=0,"Wrong unary opcode.");
      };
      opl.what=TypesStack.narrow(val,resID);
      opl.resID=resID;
    } else {
      // conversion operations
      if (opl.resID!=resID) { // don't touch objects yet
        opl.what=TypesStack.narrow(TypesStack.widen(opl.what,opl.resID),resID);
        opl.resID=resID;          
      } else if (code==11) {
        //conversion of objects
        if (tsb_to_str) opl.what=opl.what.toString();
        if (str_to_tsb) opl.what=new StringBuffer((String) opl.what);
      };
    };
    list.remove(this);
  };

  public String toString() {
    if (str_to_tsb) return "->TSB";
    if (tsb_to_str) return "TSB->";
    return opSymbols[code];
  };
  
};

