/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: ClassFile.java,v 1.3 2000/08/15 21:37:55 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.debug.Tester;
import gnu.jel.reflect.Field;
import gnu.jel.reflect.LocalField;
import gnu.jel.reflect.Method;
import gnu.jel.reflect.LocalMethod;
import gnu.jel.reflect.Member;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

// for tests
import java.io.StringReader;

/**
 * This class represents a classfile image in JEL.
 */
public class ClassFile implements Cloneable {

  // constant pool handling
  private int poolEntries=1; // Number of entries in the constant pool
  // the class name is in the first element of the CP
  // it is not written by default to enable the dynamic class renaming.
  private ByteArrayOutputStream constPoolData;
  private DataOutputStream constPool;

  // Constant pool hashing :
  // In next hashtables keys are Objects, values their CP indices
  // UTFs require special handling since their keys can be the same as
  // ones of strings
  private Hashtable Items=new Hashtable();
  private Hashtable UTFs=new Hashtable();
  
  // holds code of all methods
  int nMethods=0;
  int nMethodsPatch;
  protected PatchableByteArrayOutputStream textData;
  private DataOutputStream text;

  private boolean isInterface;

  /**
   * Starts creation of a new class file.
   * <P>Sizes of <TT>fAccess</TT>, <TT>fNames</TT> and <TT>fTypes</TT>
   * arrays must be the same.
   * @param modifiers sum of one or more of <TT>PUBLIC</TT>, <TT>FINAL</TT>,
   *                  <TT>INTERFACE</TT>, <TT>ABSTRACT</TT>
   *                  constants of java.lang.reflect.Modifier
   * @param name is the name of new class (can be null to denote that the 
   *             unique name should be automatically generated and belong to 
   *             gnu.jel.generated package)
   * @param superClass is the superclass of this class
   * @param interfaces array of interfaces this class implements
   * @param fAccess array of modifiers for each field, each modifier can be a
   *                sum of one or more of <TT>PUBLIC</TT>, <TT>PRIVATE</TT>,
   *                <TT>PROTECTED</TT>, <TT>STATIC</TT>, <TT>FINAL</TT>,
   *                <TT>VOLATILE</TT>, <TT>TRANSIENT</TT> constants defined in 
   *                java.lang.reflect.Modifier
   * @param fNames array of names of all fields.
   * @param fTypes array of Classes representing types of all fields.
   */
  public ClassFile(int modifiers,String name,Class superClass,
                   Class[] interfaces,LocalField[] fields) {
    
    constPoolData=new ByteArrayOutputStream();
    constPool=new DataOutputStream(constPoolData);
    textData=new PatchableByteArrayOutputStream();
    text=new DataOutputStream(textData);

    try {
      getUTFIndex(toHistoricalForm(name)); // must be the first entry

      if (Debug.enabled)
        Debug.assert((modifiers & ~(java.lang.reflect.Modifier.PUBLIC+
                                    java.lang.reflect.Modifier.FINAL+
                                    java.lang.reflect.Modifier.INTERFACE+
                                    java.lang.reflect.Modifier.ABSTRACT))==0);
      isInterface=((modifiers & 0x0200)>0);
      
      // write modifiers
      text.writeShort(modifiers | 0x0020); // set ACC_SUPER flag
      
      // CONSTANT_CLASS for a new class, has to be written by hand
      // since no corresponding java.lang.Class object exists yet.
      if (Debug.enabled)
        Debug.assert(poolEntries==2);
      
      poolEntries++;
      constPool.write(7);
      constPool.writeShort(1);  //name of this class is always the first entry.
      
      // write class cp index
      text.writeShort(2);
    
      // write superclass cp index
      text.writeShort(getIndex(superClass,9));
      
      // write out interfaces
      int nInterfaces;
      if (interfaces==null) nInterfaces=0; else nInterfaces=interfaces.length;
      
      text.writeShort(nInterfaces);
      for(int i=0;i<nInterfaces;i++) {
        if (Debug.enabled)
          Debug.assert(interfaces[i].isInterface());
        text.writeShort(getIndex(interfaces[i],9));
      };
      
      // write out fields
      int nFields;
      if (fields==null) nFields=0; else nFields=fields.length;
      
      text.writeShort(nFields);
      for(int i=0;i<nFields;i++) {
        LocalField cLF=fields[i];
        
        if (Debug.enabled) {
          Debug.assert((cLF.getModifiers() >>> 16)==1);
          Debug.assert(cLF.getDeclaringClass()==null); // only locals
        };
        
        String nameLM;
        text.writeShort(cLF.getModifiers() & 0x0000FFFF);
        text.writeShort(getUTFIndex(nameLM=cLF.getName()));
        text.writeShort(getUTFIndex(getSignature(cLF.getType())));
        text.writeShort(0); // no attributes
      };
      nMethodsPatch=textData.size();      
      text.writeShort(0); // methods count placeholder
    } catch (IOException exc) {
      // can't be
    };
  };

  /**
   * Makes a clone of this object.
   * @return a clone of this object
   */
  public Object clone() {
    ClassFile res=null;
    try {
      res=(ClassFile)super.clone(); // clone all primitive members

      // clone objects (shallow)
      res.Items=(Hashtable)res.Items.clone();
      res.UTFs=(Hashtable)res.UTFs.clone();
      res.paramsVars=(int [])res.paramsVars.clone();
      res.typesStk=(TypesStack) res.typesStk.clone();
      res.jumps=(IntegerStack) res.jumps.clone();
      res.jumps0=(IntegerStack) res.jumps0.clone();
      res.jumps1=(IntegerStack) res.jumps1.clone();
      res.blocks0=(IntegerStack) res.blocks0.clone();
      res.blocks1=(IntegerStack) res.blocks1.clone();
      res.branchStack=(IntegerStack) res.branchStack.clone();

      // clone streams
      res.constPoolData=new ByteArrayOutputStream();
      constPool.flush();
      constPoolData.writeTo(res.constPoolData);
      res.constPool=new DataOutputStream(res.constPoolData);

      res.textData=new PatchableByteArrayOutputStream();
      text.flush();
      textData.writeTo(res.textData);
      res.text=new DataOutputStream(res.textData);     

    } catch (IOException exc) {
      if (Debug.enabled)
        Debug.reportThrowable(exc);
    } catch (CloneNotSupportedException exc) {
      if (Debug.enabled)
        Debug.reportThrowable(exc);
    };
    return res;
  };



  private int startCodeAttr=0;
  private int startCode=0;

  /**
   * Starts a new method of this class.
   * @param m method descriptor.
   * @param vars types of local variables..
   */
  public void newMethod(LocalMethod m, Class[] vars) {

    if (Debug.enabled) {
      Debug.assert(typesStk.size()==0);
      Debug.assert(jumps0.size()==0);
      Debug.assert(jumps1.size()==0);
      Debug.assert(blocks0.size()==0);
      Debug.assert(blocks1.size()==0);
      Debug.assert(currJump==0);
    };
    
    try {
      // first finish the last method
      finishMethod();
      
      nMethods++;

      // now prepare creation of a new one.
      int mdfrs=m.getModifiers() & 0x0000FFFF;
      if (isInterface) mdfrs=mdfrs | 0x0400; // mark abstract for interfaces
      boolean isAbstract=((mdfrs & 0x0400) > 0);
      
      text.writeShort(mdfrs); // modifiers
      text.writeShort(getUTFIndex(m.getName())); // name index
      text.writeShort(getUTFIndex(getSignature(m))); // signature index
      
      int temp=0;
      Class[] exceptions=m.getExceptionTypes();
      if (exceptions.length>0) temp++; // exceptions are in separate attribute
      if (!isAbstract) temp++; // non-abstract methods have code
      text.writeShort(temp);
      
      // first we write exceptions attribute, if needed
      if ((temp=exceptions.length)>0) {
        text.writeShort(getUTFIndex("Exceptions"));
        text.writeInt((temp+1)*2); // attribute length
        text.writeShort(temp);
        for(int i=0;i<temp;i++)
          text.writeShort(getIndex(exceptions[i],9));
        // TODO: could have checked that exceptions[i] is actually subclass of
        // java.lang.Exception.
        // May be better place for this check is gnu.jel.reflect...
      };
      
      // now we start writing the code attribute
      if (!isAbstract) {
        startCodeAttr=textData.size();
        text.writeShort(getUTFIndex("Code"));
        text.writeInt(0);       // attribute length, to be patched back
        
        text.writeShort(0);     // max stack, to be patched back
        
        // local variables
        Class[] params=m.getParameterTypes();
        int parlen=(params==null?0:params.length);
        int varlen=(vars==null?0:vars.length);
        int this_num=(mdfrs & 0x0008)==0?1:0; // if not static there is "this"
        int nLocalVars=parlen+varlen+this_num;
        paramsVars=new int[nLocalVars];
        int localsSize=0;
        for (int i=0;i<paramsVars.length;i++) {
          int j=i-this_num;
          int typeID=i<this_num?8:
            TypesStack.primitiveID(j<parlen?params[j]:vars[j-parlen]);
          paramsVars[i]=localsSize;
          localsSize+=TypesStack.stkoccup[typeID];
        };
        
        text.writeShort(localsSize);  // number of local vars
        
        text.writeInt(0);      // code length, to be patched back
        startCode=textData.size();
      };
      
      typesStk.resetStats();
      
      if (!isAbstract) currMethod=m;
    } catch (IOException exc) {
      // can't be
    };
  };

  private void finishMethod() throws IOException {
    if (currMethod!=null) { // finish the previous method
      int codeEnd=textData.size();
      text.writeShort(0); // no exception table       // TODO: add exceptions
      text.writeShort(0); // no attributes
      int currPos=textData.size();
      
      // Code attribute length
      textData.patchAddressInt(startCodeAttr+2,currPos-startCodeAttr-6);

      // stack size
      textData.patchAddress(startCodeAttr+6,typesStk.getMaxOccupation());

      // code size
      textData.patchAddress(startCode-2,codeEnd-startCode);

      currMethod=null;
    };
  };

  // adds one or more instructions to the code.
  //  public void asmOP(OP op);

  private static final byte[] prologue = 
  {(byte)0xCA,(byte)0xFE,(byte)0xBA,(byte)0xBE,0x00,0x03,0x00,0x2D};

  // finishes class and returns the resulting bytecode
  public byte[] getImage() {
    ByteArrayOutputStream image=new ByteArrayOutputStream();

    try {
      finishMethod(); // finish the last method
      text.writeShort(0); // no class file attributes

      // patch methods count
      textData.patchAddress(nMethodsPatch,nMethods);      
      
      // assemble the method bytecode
      image.write(prologue);
      image.write((poolEntries >>> 8) & 0xFF);
      image.write((poolEntries >>> 0) & 0xFF);
      constPoolData.writeTo(image);
      textData.writeTo(image);
    } catch (IOException exc) {
      // can't be
    };
    return image.toByteArray();
  };
  
  //=========================================================
  //========== INSTRUCTIONS INTERFACE TO THE STATE===========
  //=========================================================
  protected LocalMethod currMethod=null;
  int[] paramsVars=null; // correspondence between parameter number and lvars.

  // Types in Java stack
  protected TypesStack typesStk=new TypesStack();

  // jump currently being generated(its code is here)
  protected int currJump = 0;
  protected boolean invert_next_jump=false;
  
  // Branches
  private IntegerStack jumps=new IntegerStack();
  private IntegerStack jumps0=new IntegerStack();
  private IntegerStack jumps1=new IntegerStack();
  private IntegerStack blocks0=new IntegerStack();
  private IntegerStack blocks1=new IntegerStack();
  private IntegerStack branchStack=new IntegerStack();

  //////////////////////// BRANCHES HANDLING \\\\\\\\\\\\\\\\\\\\\\\\

  private final static int invert_jump_bytecode(int jmp) {
    if (Debug.enabled)
      Debug.assert((jmp>=153) && (jmp<=164),
                   "Attempt to invert non jump bytecode ("+jmp+")");
    return (((jmp-1) ^ 0x0001)+1);
  };
  
  protected void labels_block() {
    blocks0.push(jumps0.size());
    blocks1.push(jumps1.size());
  };

  protected void labels_unblock() {
    blocks0.pop_throw();
    blocks1.pop_throw();
  };

  protected void labels_unblock_not() {
    ensure_jump();
    IntegerStack.swap(jumps1,blocks1.pop(),jumps0,blocks0.pop());
    invert_next_jump=!invert_next_jump;
  };
  
  // Makes sure that currently jump is in progress
  // as with binary comparizon ops jump in progress is always
  // a jump to "true" label.
  protected final void ensure_jump() {
    if (currJump!=0) return;
    Class a1=typesStk.pop();
    if (Debug.enabled)
      Debug.assert(a1==Boolean.TYPE,"Only booleans can generate jumps in "+
                   "ensurejump()");
    currJump=157; //|
  };
  
  // ensures that the boolean is now in java stack and all unblocked 
  // labels were landed
  // this should be called every time the boolean value is used :
  // before giving it as a parameter to a function; before returning the 
  // boolean value.
  protected final void ensure_value() {
    if (currJump==0) { 
      // check if there are pending jumps
      int blocked0=0;
      if (blocks0.size()>0) blocked0=blocks0.peek();
      
      int blocked1=0;
      if (blocks1.size()>0) blocked1=blocks1.peek();
      
      boolean noPendingJumps=(jumps0.size()==blocked0) && 
        (jumps1.size()==blocked1);
      
      // nothing in progress, no pending -- value already in stack
      if (noPendingJumps) return; 
    };
    
    branch_true();
    codeLDC(Boolean.TRUE,0);
    branch_false();
    codeLDC(Boolean.FALSE,0);
    branch_end();
  };


  private final void mkLabel(IntegerStack jumps) {
    int currpos=textData.size();
    jumps.push(currpos);
    codeI(0);              // placeholder for the backpatched address
  };

  private final void landLabel(IntegerStack jumps) {
    int currpos=textData.size();
    int addrpos=jumps.pop();
    
    // in the next command -1 because displacement is counted from the
    // jump opcode
    textData.patchAddress(addrpos,currpos-addrpos+1); 
  };
  
  private final void landLabels(IntegerStack jumps,IntegerStack blocks) {
    int blocked_at=0;
    if (blocks.size()>0) blocked_at=blocks.peek();
    while (jumps.size()>blocked_at) landLabel(jumps);
  };
  
  /**
   * Starts generation of code when condition is "true". 
   */
  public void branch_true() {
    ensure_jump(); // true jump first
    
    if (!invert_next_jump)
      currJump=invert_jump_bytecode(currJump);
    invert_next_jump=false;
    code(currJump);
    currJump=0;
    mkLabel(jumps0); 
    landLabels(jumps1,blocks1);
    blocks0.push(jumps0.size()); // block true jumps
    // Size of typesStk before first branch
    branchStack.push(typesStk.size());
  };

  /**
   * Starts generation of code when condition is "true".
   */
  public void branch_false() {
    ensure_value();
    blocks0.pop_throw(); // unblock true jumps
    
    int beforeStk=branchStack.pop();
    
    if (Debug.enabled)
      branchStack.push(typesStk.currWords);

    // remove the result of the previous branch, the other branch must
    // put the same thing back into the types stack
    while (beforeStk<typesStk.size()) typesStk.pop();
    
    code(167);   //|   goto
    mkLabel(jumps);
    landLabels(jumps0,blocks0);
  };

  /**
   * Finishes generation of code for conditional.
   * @see gnu.jel.ClassFile#branch_true
   */
  public void branch_end() {
    ensure_value();
    if (Debug.enabled)
      Debug.assert(branchStack.pop()==typesStk.currWords,
                   "Stack mismatch when compiling conditional");
    landLabel(jumps);
  }; 

  protected void logical_param(boolean and) {
    IntegerStack j0,j1,b0,b1;
    if (and) {
      j0=jumps0; j1=jumps1; b0=blocks0;b1=blocks1;
    } else {
      j0=jumps1; j1=jumps0; b0=blocks1; b1=blocks0;
    };
    
    ensure_jump();
    if (invert_next_jump ^ and)
      currJump=invert_jump_bytecode(currJump);
    invert_next_jump=false;
    code(currJump);
    currJump=0;
    mkLabel(j0);
    landLabels(j1,b1);
    b0.push(j0.size()); //  block labels in j0
  };

  protected void logical_end(boolean and) {
    if (and) 
      blocks0.pop_throw();
    else
      blocks1.pop_throw();
  };
  
  // code up to 4 operations
  protected final void code(int op) {
    try {
      while (op!=0) {
        if (Debug.enabled)
          Debug.assert((op & 0x000000FF) !=0xFF);
        text.write(op & 0x000000FF);
        op=op >>> 8;
      };
    } catch (java.io.IOException e) {
      // Can't be
    };
  };

  // Shortcut load opcodes for int type
  // index is value+1; allowed values from -1 to 5.
  // other values should be loaded from CP.
  private final static int[] load_ints=
  {  0x02,  0x03,  0x04,  0x05,  0x06,  0x07,  0x08};
  //  -1     0      1      2      3      4      5
  private final static int[] load_long_ints=
  {0x8502,  0x09,  0x0A,0x8505,0x8506,0x8507,0x8508};
  //  -1     0      1      2      3      4      5

  // gen code for code loading constant of primitive type or string
  protected final void codeLDC(Object o, int primitiveID) {
    if (Debug.enabled)  
      Debug.assert((primitiveID>=0) && (primitiveID<=8) && 
                   ((primitiveID!=8) || (o==null) || (o instanceof String)));
    
    int short_opcodes=0;
    
    int iv=-1;
    switch (primitiveID) {
    case 0:
      iv=(((Boolean)o).booleanValue()?1:0);
    case 2:
      if (iv<0) iv=(int)((Character)o).charValue();
    case 1:
    case 3:
    case 4:
      if (iv<0) iv=((Number)o).intValue();
      if ((iv>=-1) && (iv<=5))
        short_opcodes=iv+3; //|  iconst_<i>
      else if ((iv>=-128) && (iv<=127))
        short_opcodes=0x00000010 | ((iv & 0xFF)<<8); //| bipush, <value>
      break;
    case 5:
      long lv=((Long)o).longValue();
      if ((lv>=-1) && (lv<=5))
        short_opcodes=load_long_ints[(int)lv+1];
      break;
    case 6:
      float fv=((Float)o).floatValue();
      if (fv==0.0f) short_opcodes=0x0B;           //| fconst_0
      else if (fv==1.0f) short_opcodes=0x0C;      //| fconst_1
      else if (fv==2.0f) short_opcodes=0x0D;      //| fconst_2
      break;
    case 7:
      double dv=((Double)o).doubleValue();
      if (dv==0.0) short_opcodes=0x0E;      //| dconst_0
      else if (dv==1.0) short_opcodes=0x0F; //| dconst_1
      break;
    case 8:
      if (o==null) short_opcodes=0x01;      //| aconst_null
      break;
    default:
      if (Debug.enabled) 
        Debug.assert(false,"Loading of object constants is not supported by "+
                     "the Java class files.");
    };
    
    if (short_opcodes==0) {
      try {
        // longs and doubles occupy two elements of stack all others just one
        boolean dword_const=((primitiveID==5) || (primitiveID==7));

        // makes use of the fact that primitiveID(Object)==typeID(string)
        int cpindex=getIndex(o,primitiveID);

        if (Debug.enabled)  
          Debug.assert((cpindex>=0) && (cpindex<=65535));

        if ((!dword_const) && (cpindex<=255)) {
          text.write(0x12);          //|   ldc
          text.write(cpindex);
        } else {
          int opc=0x13;                        //|   ldc_w
          if (dword_const) opc++;              //|   ldc2_w
          text.write(opc);
          text.writeShort(cpindex);
        };
      } catch (java.io.IOException e) {
        // Can't be
      };
    } else code(short_opcodes);
    
    if (primitiveID!=8)
      typesStk.pushID(primitiveID);
    else
      typesStk.push(o!=null?o.getClass():null); // java.lang.String

  };

  // code integer (2 byte) reference
  protected final void codeI(int ind) {
    if (Debug.enabled)  Debug.assert((ind>=0) && (ind<=65535));
    try {
      text.writeShort(ind);
    } catch (java.io.IOException e) {
      // Can't be
    };
  };

  //=========================================================
  //================= CONSTANTS POOL HANDLING ===============
  //=========================================================

  /**
   * Used to get the index of the given UTF8 string in the Constant Pool.
   * <P> If the specified string was not found in the pool -- it is added.
   * @param str the string to look for ( to add )
   * @return index of the string in the Constant Pool.
   */
  private int getUTFIndex(String str) {
    // Check if it is in the pool already
    Integer index=(Integer)UTFs.get(str);
    if (index==null) {    // add UTF to the pool
      index=new Integer(poolEntries++);
      try {
        constPool.write(1);            // CONSTANT_Utf8 = 1;
        constPool.writeUTF(str);
      } catch (java.io.IOException e) {
        if (Debug.enabled) Debug.reportThrowable(e);
      };
      UTFs.put(str,index);
    };
    return index.intValue();
  };

  // encodes types of relevant objects as integers
  // for classes corresponding to primitive types codes are the same as 
  // primitiveID's
  private int typeID(Object item) {
    int id=TypesStack.primitiveID(item);
    if (id<8) return id;
    if (item instanceof String) return 8;
    if (item instanceof Class) return 9;
    if (item instanceof Member) return 10;
    return -1;
  };

  /**
   * Used to determine an old CP index or to create a new one for an item.
   * @param item an item to create or get an index for
   * @return index for an item (negative if it has to be written)
   */
  private final int getIndex(Object item) {
    return getIndex(item,typeID(item));
  };

  /**
   * Used to determine an old CP index or to create a new one for an item.
   * @param item an item to create or get an index for
   * @param typeid identifies type of argument to avoid linear searches
   * @return index for an item (negative if it has to be written)
   */
  protected int getIndex(Object item,int typeid) {

    Integer index=(Integer)Items.get(item);
    if (index==null) {
      int newIndex=-1;

      try {
        int ival=-1;
        switch (typeid) {
        case 0:
          ival=((Boolean)item).booleanValue()?1:0;
        case 2:
          if (ival<0) ival=(int)((Character)item).charValue();
        case 1:
        case 3:
        case 4:
          if (ival<0) ival=((Number)item).intValue();
          newIndex=poolEntries++;
          constPool.write(3);                  // CONSTANT_Integer = 3;
          constPool.writeInt(ival);
          break;
        case 5:
          newIndex=poolEntries;
          constPool.write(5);                  //  CONSTANT_Long = 5;
          constPool.writeLong(((Long)item).longValue());
          poolEntries+=2; // Long occupies two entries in CP, weird !!!
          break;
        case 6:
          newIndex=poolEntries++;
          constPool.write(4);   //       CONSTANT_Float = 4;
          constPool.writeFloat(((Float)item).floatValue());
          break;
        case 7:
          newIndex=poolEntries;
          constPool.write(6);   //       CONSTANT_Double = 6;
          constPool.writeDouble(((Double)item).doubleValue());
          poolEntries+=2; // Double occupies two entries in CP, weird !!!
          break;
        case 8:
          {
            int UTFIndex=getUTFIndex((String)item); // write string , if needed
            newIndex=poolEntries++;
            constPool.write(8); //       CONSTANT_String = 8;
            constPool.writeShort(UTFIndex);
          }
          break;
        case 9:
          {
            String histNameStr=toHistoricalForm(((Class)item).getName());
            int UTFIndex=getUTFIndex(histNameStr); // write FQCN , if needed
            newIndex=poolEntries++;
            constPool.write(7); //       CONSTANT_Class = 7;
            constPool.writeShort(UTFIndex);
          }
          break;
        case 10: // Method
        case 11: // Constructor
        case 12: // Field
          Member member = (Member) item;
          Class dClass=member.getDeclaringClass();
          int entryType;
          if ((member.getModifiers() >>> 16) == 1) // field
            entryType=9;  //          CONSTANT_Fieldref = 9;
          else if ((dClass!=null) && (dClass.isInterface()))
            entryType=11; //          CONSTANT_InterfaceMethodref = 11;
          else
            entryType=10; //          CONSTANT_Methodref = 10;
          
          newIndex=writeMemberRef(member,entryType);
          break;
        default:
          if (Debug.enabled)
            Debug.println("Can't place an item of type \""+
                          item.getClass().getName()+
                          "\" to the constant pool.");
        };
      } catch (java.io.IOException e) {
        if (Debug.enabled) Debug.reportThrowable(e);
      };
      index=new Integer(newIndex);
      Items.put(item,index);
    };
    return index.intValue();
  };

  // writes out full reference to method, interface or field
  // this includes UTFs, Name&Type and XXX_ref entries
  private int writeMemberRef(Member member, int entry) 
    throws java.io.IOException {
    if (Debug.enabled)
      Debug.assert((entry==10)||(entry==9)||(entry==11));
    //  CONSTANT_Fieldref = 9;  CONSTANT_Methodref = 10; 
    //  CONSTANT_InterfaceMethodref = 11;
    
    int name_ind=getUTFIndex(member.getName());
    int sign_ind=getUTFIndex(getSignature(member));

    Class dClass=member.getDeclaringClass();
    int cls_ind;
    if (dClass==null) cls_ind=2;  else  cls_ind=getIndex(dClass,9); 
    //  this class ---^^^^^^^^^                  9 means Class--^
    
    // Create Name and Type record
    int nat_ind=poolEntries++;
    constPool.write(12);                //   CONSTANT_NameAndType = 12;
    constPool.writeShort(name_ind);
    constPool.writeShort(sign_ind);

    // Create XXX_ref entry (where XXX is InterfaceMethod, Method or field)    
    int index=poolEntries++;
    constPool.write(entry);
    constPool.writeShort(cls_ind);
    constPool.writeShort(nat_ind);

    return index;
  };

  /**
   * Computes signature of the given member.
   * @param m the member to compute the sugnature of.
   * @return the signature.
   */
  public static String getSignature(Member m) {
    StringBuffer signature=new StringBuffer();
    if ((m.getModifiers() >>> 16) >1) { // not a field
      Class parameters[]=m.getParameterTypes();
      signature.append('(');
      for(int i=0;i<parameters.length;i++) 
        appendSignature(signature,parameters[i]);
      signature.append(')');
    };
    appendSignature(signature,m.getType());
    return signature.toString();
  };

  /**
   * Computes the signature of the given class.
   * <P> The signature of the class (Field descriptor) is the string and 
   * it's format is described in the paragraph 4.3.2 of the Java VM 
   * specification (ISBN 0-201-63451-1).
   * <P>The same can be done using <TT>java.lang.Class.getName()</TT> by 
   * converting it's result into the "historical form".
   * <P> This utility method can be used outside of the JEL package
   * it does not involve any JEL specific assumptions and should follow
   * JVM Specification precisely.
   * @param cls is the class to compute the signature of. Can be primitive or
   *            array type.
   * @return the class signature.
   */
  public static String getSignature(Class cls) {
    return appendSignature(new StringBuffer(),cls).toString();
  };

  private static StringBuffer appendSignature(StringBuffer buff, Class cls) {
    if (cls.isPrimitive()) 
      buff.append(TypesStack.primitiveCodes[TypesStack.primitiveID(cls)]);
    else if (cls.isArray()) {
      buff.append('[');
      appendSignature(buff,cls.getComponentType());
    } else { // just a class
      buff.append('L');
      appendHistoricalForm(buff,cls.getName());
      buff.append(';');
    };
    return buff;
  };

  private static String toHistoricalForm(String className) {
    return appendHistoricalForm(new StringBuffer(),className).toString();
  };

  private static StringBuffer appendHistoricalForm(StringBuffer buff,
                                                   String className) {
    int namelen=className.length();
    for(int i=0;i<className.length();i++) {
      char cch=className.charAt(i);
      if (cch=='.') cch='/';
      buff.append(cch);
    };
    return buff;
  };

  //================================================================
  //================== END OF CONSTANT POOL HANDLING ===============
  //================================================================

  //================================================================
  //======================== UNITARY TESTS =========================
  //================================================================

  /**
   * Performs unitary test of the code generator.
   * @param args ignored.
   */
  public static void main(String[] args) {
    if (Debug.enabled) {
      Tester t=new Tester(System.out);
      test(t);
      t.summarize();
    };
  };

  static void dumpImage(ClassFile cf) {
    if (Debug.enabled) {
      try {
        java.io.FileOutputStream fos=
          new java.io.FileOutputStream("dump.class");
        fos.write(cf.getImage());
        fos.close();
      } catch (Exception e) {
        Debug.println("Can't dump generated class file.");
      };
    };
  };

  /**
   * Performs unitary test of the code generator.
   * <p> Used if all package is being tested and not just codegen.
   * @param t Tester to report test results.
   */
  public static void test(Tester t) {
    if (Debug.enabled) {
      // Utility classes tests
      {
        t.startTest("toHistoricalForm(\"java.lang.String\")");
        t.compare(toHistoricalForm("java.lang.String"),
                  "java/lang/String");

        t.startTest("getSignature((\"a string\").getClass())");
        t.compare(getSignature(("a string").getClass()),
                  "Ljava/lang/String;");
	
        t.startTest("getSignature((new int[10]).getClass())");
        t.compare(getSignature((new int[10]).getClass()),
                  "[I");
	
        t.startTest("getSignature((new Object[10]).getClass())");
        t.compare(getSignature((new Object[10]).getClass()),
                  "[Ljava/lang/Object;");

      };

      // Constant pool filling test
      {

        LocalField[] lf=new LocalField[1];
        // private Object[] e;
        lf[0]=new LocalField(0x0002,(new Object[0]).getClass(),"e",null);

        // public class <autoname> extends java.lang.Object {
        ClassFile cf=
          new ClassFile(0x0001,"dump",(new Object()).getClass(),null,lf);

        t.startTest("Add UTF twice");
        String s1="some string";
        int si=cf.getUTFIndex(s1);
        int sii=cf.getUTFIndex(s1);
        t.compare(sii,si);

        t.startTest("Add Long twice");
        int li=cf.getIndex(new Long(15),5);
        int lii=cf.getIndex(new Long(15),5);
        t.compare(lii,li);

        t.startTest("Add Integer twice");
        int ii=cf.getIndex(new Integer(15),4);
        int iii=cf.getIndex(new Integer(15),4);
        t.compare(iii,ii);

        t.startTest("Add Float twice");
        int ff=cf.getIndex(new Float(15.0f),6);
        int fff=cf.getIndex(new Float(15.0f),6);
        t.compare(fff,ff);

        t.startTest("Add Double twice");
        int di=cf.getIndex(new Double(15.0),7);
        int dii=cf.getIndex(new Double(15.0),7);
        t.compare(dii,di);

        t.startTest("Add a new String twice");
        String s2="some other string";
        int s2i=cf.getIndex(s2,8);
        int s2ii=cf.getIndex(s2,8);
        t.compare(s2ii,s2i);

        t.startTest("Add a string with existing UTF twice");
        int s3i=cf.getIndex(s1,8);
        int s3ii=cf.getIndex(s1,8);
        t.compare(s3ii,s3i);
	
        t.startTest("Add a class twice");
        int ci=cf.getIndex(cf.getClass(),9);
        int cii=cf.getIndex(cf.getClass(),9);
        t.compare(cii,ci);
	
        t.startTest("Add a method twice");
        Class[] params=new Class[1];
        try {
          Method a_method;
          params[0]=Class.forName("gnu.jel.debug.Tester");
          a_method=new Method(cf.getClass().getMethod("test",params));
          int mi=cf.getIndex(a_method,10);
          int mii=cf.getIndex(a_method,10);
          t.compare(mii,mi);
        } catch (Exception e) {
          t.testFail();
          Debug.reportThrowable(e);
        };
        
        Library lib=null;
        Object[] dynalib=null;
        t.startTest("Construct a library of java.lang.Math functions");
        try {
          Class[] stat=new Class[1];
          stat[0]=Class.forName("java.lang.Math");
          Class[] dyn=new Class[1];
          dyn[0]=Class.forName("java.lang.Double");
          lib=new Library(stat,dyn);

          dynalib=new Object[1];
          dynalib[0]=new Double(100.0);
          t.testOK();
        } catch (Exception e) {
          t.testFail();
          Debug.reportThrowable(e);
        };
        
        byte[] image=null;
        LocalMethod[] eval_methods=
          new LocalMethod[TypesStack.primitiveTypes.length];
        ClassFile cf_orig=null;
        int retID_patchback=0;
        
        t.startTest("Construct a compiled expression subclass");
        try {
          // prepare eval methods
          Class[] paramsE=new Class[1];
          paramsE[0]=(new Object[0]).getClass();
          for(int i=0;i<TypesStack.primitiveTypes.length-1;i++) {
            String name="evaluate";
            Class cls=TypesStack.primitiveTypes[i];
            if (i!=8) 
              name=name+'_'+TypesStack.primitiveTypeNames[i];
            else 
              cls=(new Object()).getClass();
            eval_methods[i]=new LocalMethod(0x0001,cls,name,paramsE,null);
          };

          Class cmplExpr=Class.forName("gnu.jel.CompiledExpression");
          cf=new ClassFile(0x0001,"dump",cmplExpr,null,lf);
          // public 
          LocalMethod cnstr=
            new LocalMethod(0x0001,Void.TYPE,"<init>",null,null);
          cf.newMethod(cnstr,null);
          cf.code(0x2a);                //| aload_0  ;loads "this"
          cf.typesStk.push(null);
          Method supInit=new Method(cmplExpr.getConstructor(new Class[0]));
          cf.code(0xb7);                //| invokespecial
          cf.codeI(cf.getIndex(supInit));  //|    super();
          cf.typesStk.pop();
          cf.code(0xb1);                //| return void

          LocalMethod getType=
            new LocalMethod(0x0001,Integer.TYPE,"getType",null,null);
          cf.newMethod(getType,null);
          cf.code(0x10);                //| bipush
          retID_patchback=cf.textData.size();
          cf.code(8);                   //    type placeholder
          cf.typesStk.push(Integer.TYPE);
          
          cf.code(0xAC);                //| ireturn
          cf.typesStk.pop();

          cf_orig=(ClassFile)cf.clone();
          
          cf.newMethod(eval_methods[8],null);
          cf.code(0x01);                //| aconst_null
          cf.typesStk.push(null);
          cf.code(0xB0);                //| areturn
          cf.typesStk.pop();
          
          image=cf.getImage();
          t.testOK();
        } catch (Exception e) {
          t.testFail();
          Debug.reportThrowable(e);
        };

        t.startTest("Load and execute constructed class");
        try {
          dumpImage(cf);
          CompiledExpression expr= 
            (CompiledExpression)(ImageLoader.load(image)).newInstance();
          boolean ok=((expr.getType()==8) && (expr.evaluate(null)==null));
          if (ok) t.testOK(); else t.testFail();
        } catch (Throwable e) {
          t.testFail();
          Debug.reportThrowable(e);
        };

        exprTest("1 (I)",dynalib,lib,new Integer(1),
                 cf_orig,retID_patchback,eval_methods,t,false);
        
        exprTest("1 -- (I)",dynalib,lib,new Integer(-1),
                 cf_orig,retID_patchback,eval_methods,t,false);

        // Passing constants of primitive types.
        // this also tests shortcut commands for loading some integer
        // and floating point constants
        exprTest("1 --",dynalib,lib,new Integer((byte)-1),
                 cf_orig,retID_patchback,eval_methods,t,false);
        exprTest("1L --",dynalib,lib,new Long(-1),
                 cf_orig,retID_patchback,eval_methods,t,false);
        for(byte i=0;i<=6;i++) {
          exprTest(String.valueOf(i),dynalib,lib,new Byte(i),
                   cf_orig,retID_patchback,eval_methods,t,false);
          exprTest(String.valueOf(i)+'L',dynalib,lib,new Long(i),
                   cf_orig,retID_patchback,eval_methods,t,false);
        };

        for(byte i=0;i<=3;i++) {
          exprTest(String.valueOf(i)+".0F",dynalib,lib,new Float(i),
                   cf_orig,retID_patchback,eval_methods,t,false);
          exprTest(String.valueOf(i)+".0",dynalib,lib,new Double(i),
                   cf_orig,retID_patchback,eval_methods,t,false);
        };
        exprTest("true",dynalib,lib,Boolean.TRUE,
                 cf_orig,retID_patchback,eval_methods,t,false);
        exprTest("false",dynalib,lib,Boolean.FALSE,
                 cf_orig,retID_patchback,eval_methods,t,false);
        
        // this tests immediate byte constants loading (1 byte in the code)
        // and loading through the CP.
        for(int i=126;i<=128;i++) {
          exprTest(String.valueOf(i)+" (I)",dynalib,lib,new Integer(i),
                   cf_orig,retID_patchback,eval_methods,t,false);
          exprTest(String.valueOf(i)+" -- (I)",dynalib,lib,new Integer(-i),
                   cf_orig,retID_patchback,eval_methods,t,false);
        };

        exprTest("( 1 , 2 , min)",dynalib,lib,new Integer(1),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( ( E) , sin)",dynalib,lib,new Double(Math.sin(Math.E)),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("2 , 2 , *",dynalib,lib,new Integer(4),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("3 , 2 , * , 1 , -",dynalib,lib,new Integer(5),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("3 , 2 , * , 1 , - , 3 , - , 2 , *",dynalib,lib,
                 new Integer(4),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("3 , 2 , * , 1 , - , 3 , - , 2 , * , 4 , ==",dynalib,lib,
                 Boolean.TRUE,
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("\"a\" , \"b\" , + , 4 , + , true , +",dynalib,lib,
                 "ab4true",
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("2 , 3 , > , 3 , 2 , >= , ||",dynalib,lib,
                 Boolean.TRUE,
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( isNaN)",dynalib,lib,
                 Boolean.FALSE,
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("1 , ( doubleValue) , + , 101 , ==",dynalib,lib,
                 Boolean.TRUE,
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( true ? 1 : 2 ) (I)",dynalib,lib,
                 new Integer(1),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( false ? 1 : 2 ) (I)",dynalib,lib,
                 new Integer(2),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( false ? 1 : 2 , 2 , + ) (I)",dynalib,lib,
                 new Integer(4),
                 cf_orig,retID_patchback,eval_methods,t,false);

        exprTest("( true ? ( 1 , 3 , + , 5 , min) : 2 ) (I)",dynalib,lib,
                 new Integer(4),
                 cf_orig,retID_patchback,eval_methods,t,true);

                
      };
    };
  };

  private static void exprTest(String expr, Object[] thisPtrs, Library lib,
                               Object expRes, ClassFile cf_orig,
                               int retID_patchback,
                               LocalMethod[] eval_methods,Tester t,
                               boolean verbose) {
    if (Debug.enabled) try {
      StringBuffer testTitle=new StringBuffer();
      {
        for(int i=0;i<expr.length();i++)
          if (expr.charAt(i)!=' ') testTitle.append(expr.charAt(i));
        testTitle.append(" == ");
        int id=TypesStack.primitiveID(expRes);
        if (id<8) {
          testTitle.append(expRes);
          testTitle.append(TypesStack.primitiveCodes[id]);
        } else if (expRes instanceof String) {
          testTitle.append('"');
          testTitle.append(expRes);
          testTitle.append('"');
        } else testTitle.append(expRes);
      }

      t.startTest(testTitle.toString());
      TypesStack typesStk=new TypesStack();
      Stack paramOPs=new Stack();
      OPlist list=new OPlist();
      IntegerStack paramsStart=new IntegerStack();
      Stack oldLists=new Stack();
      IntegerStack branchStack=new IntegerStack();
      
      OP cop;

      try {
        StringReader sr=new StringReader(expr);
        StringBuffer cToken=new StringBuffer();
        int cChar;
        while ((cChar=sr.read())>0) {
          
          // skip whitespace
          while ((((char)cChar)==' ') && ((cChar=sr.read())>0));
        
          // get the next token
          cToken.setLength(0); // clear the last token
          while ((cChar>0) && (((char)cChar)!=' ')) {
            cToken.append((char) cChar);
            cChar=sr.read();
          };
        
          if (cToken.length()>0) { // single symbol token
            char cTok=cToken.charAt(0);
            switch (cTok) {
            case '~':
              list.addLast(new OPunary(typesStk,1));
              break;
            case ',':
              paramOPs.push(list.getLast());
              break;
            case '(': // can be type conversion or function
              if (cToken.length()==1) { // function
                if (list.size()>0)
                  paramOPs.push(list.getLast());
                else
                  paramOPs.push(null);
                paramsStart.push(typesStk.size());
              } else if (cToken.length()==3) { // type conversion
                char ttype=cToken.charAt(1);
                int tid;
                for(tid=0;(tid<TypesStack.primitiveCodes.length) && 
                      (ttype!=TypesStack.primitiveCodes[tid]);tid++);
                list.addLast(new OPunary(typesStk,tid,null,true));
              } else Debug.println("Wrong bracketed token \""+cToken+"\".");
              break;
            case '?':
              paramOPs.pop(); // one generated by a bracket
              branchStack.push(typesStk.size());
              oldLists.push(list);
              list=new OPlist();
              break;
            case ':':
              // throw items accumulated in stack
              branchStack.push(typesStk.size());
              oldLists.push(list);
              list=new OPlist();
              break;
            case ')':
              if ((branchStack.peek()+1!=typesStk.size()) ||
                  (branchStack.pop()!=branchStack.pop()+1))
                Debug.println("Stack mismatch when compiling conditional.");
              OP opcond=new OPcondtnl(typesStk,(OPlist)oldLists.pop(),list);
              list=(OPlist)oldLists.pop();
              list.addLast(opcond);
              break;
            case '+':
              list.addLast(new OPbinary(typesStk,paramOPs,0,list));
              break;
            case '-':
              if (cToken.length()==1) {
                list.addLast(new OPbinary(typesStk,paramOPs,1,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='-'))
                list.addLast(new OPunary(typesStk,0));
              else Debug.println("Wrong token \""+cTok+"\".");
              break;
            case '*':
              list.addLast(new OPbinary(typesStk,paramOPs,2,list));
              break;
            case '/':
              list.addLast(new OPbinary(typesStk,paramOPs,3,list));
              break;
            case '%':
              list.addLast(new OPbinary(typesStk,paramOPs,4,list));
              break;
            case '^':
              list.addLast(new OPbinary(typesStk,paramOPs,7,list));
              break;
            case '=':
              if (cToken.charAt(1)=='=')
                list.addLast(new OPbinary(typesStk,paramOPs,8,list));
              break;
            case '!':
              if (cToken.length()==1) {
                list.addLast(new OPunary(typesStk,2));                
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
                list.addLast(new OPbinary(typesStk,paramOPs,9,list));
              } else Debug.println("Wrong ! token \""+cTok+"\".");
              break;
            case '<':
              if (cToken.length()==1) {
                list.addLast(new OPbinary(typesStk,paramOPs,10,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
                list.addLast(new OPbinary(typesStk,paramOPs,13,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='<')) {
                list.addLast(new OPbinary(typesStk,paramOPs,14,list));
              } else Debug.println("Wrong < token \""+cTok+"\".");
              break;
            case '>':
              if (cToken.length()==1) {
                list.addLast(new OPbinary(typesStk,paramOPs,11,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
                list.addLast(new OPbinary(typesStk,paramOPs,12,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='>')) {
                list.addLast(new OPbinary(typesStk,paramOPs,15,list));
              } else if ((cToken.length()==3) && (cToken.charAt(2)=='>')) {
                list.addLast(new OPbinary(typesStk,paramOPs,16,list));
              } else Debug.println("Wrong > token \""+cTok+"\".");
              break;
            case '&':
              if (cToken.length()==1) {
                list.addLast(new OPbinary(typesStk,paramOPs,5,list));
              } else if ((cToken.length()==3) && (cToken.charAt(1)=='&')) {
                list.addLast(new OPbinary(typesStk,paramOPs,17,list));
              } else Debug.println("Wrong & token \""+cTok+"\".");
              break;
            case '|':
              if (cToken.length()==1) {
                list.addLast(new OPbinary(typesStk,paramOPs,6,list));
              } else if ((cToken.length()==2) && (cToken.charAt(1)=='|')) {
                list.addLast(new OPbinary(typesStk,paramOPs,18,list));
              } else Debug.println("Wrong | token \""+cTok+"\".");
              break;
            case '[':
              if ((cToken.length()==3) && (cToken.charAt(1)==']'))
                list.addLast(new OPbinary(typesStk,paramOPs,19,list));
              else Debug.println("Wrong [ token \""+cTok+"\".");
              break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9': // numbers
              {
                String sval=cToken.toString();
                if (sval.indexOf('.')>0) { // Floating point literals
                  char lc=Character.toUpperCase(sval.charAt(sval.length()-1));
                  boolean makeFloat = (lc=='F');
                  String svalue=sval;
                  if ((lc=='D') || (lc=='F')) {
                    svalue=svalue.substring(0,svalue.length()-1);
                  };
                  Double value=null;
                  try {
                    value=new Double(svalue);
                  } catch (NumberFormatException e) {
                    Debug.println("Can;t parse \""+svalue+
                                  "\" as a floating point number.");
                  };

                  Object otl=null;
                  Class otlc=null;
                
                  if (makeFloat) {
                    otl=new Float(value.floatValue());
                    otlc=Float.TYPE;
                  } else {
                    otl=value;
                    otlc=Double.TYPE;
                  };
                  list.addLast(new OPload(typesStk,otlc,otl));                
                } else { // integer literals
                  String svalue=sval.toUpperCase();
                  long value=0;
                  boolean makelong=svalue.endsWith("L");
                  if (makelong) svalue=svalue.substring(0,svalue.length()-1);
 
                  try {
                    if ( svalue.startsWith("0X") ) {
                      // Hexadecimal number
                      svalue=svalue.substring(2);
                      value=Long.parseLong(svalue,16);
                    } else if (svalue.startsWith("0")) {
                      // Octal number
                      value=Long.parseLong(svalue,8);
                    } else {
                      // Decimal number
                      value=Long.parseLong(svalue,10);
                    };
                  } catch (NumberFormatException e) {
                    Debug.println("Number \""+svalue+
                                  "\" is too large, it does not fit even "+
                                  "into 64 bit long."); // Overflow ?
                  };

                  Object otl=null;
                  Class otlc=null;
                  if (!makelong) { // Check ranges
                    if (value<=127) {
                      otl=new Byte((byte)value);
                      otlc=Byte.TYPE;
                    } else if (value<=32767) {
                      otl=new Short((short)value);
                      otlc=Short.TYPE;
                    } else if (value<=2147483647) {
                      otl=new Integer((int)value);
                      otlc=Integer.TYPE;
                    } else
                      Debug.println("Integer number \""+svalue+
                                    "\" is too large for type 'int'. Be sure"+
                                    " to add 'L' suffix to use 'long' type.");
                  } else {
                    otl=new Long(value);
                    otlc=Long.TYPE;
                  };
                  list.addLast(new OPload(typesStk,otlc,otl));
                };
              };
              break;
            case '\'': // char token
              {
                String sval=cToken.toString().substring(1,cToken.length()-1);
                char chr=sval.charAt(0);
                if (sval.length()!=1) { // escape or number
                  char ec=sval.charAt(1);
                  try {
                    switch (ec) {
                    case 'n': ec='\n'; break;
                    case 't': ec='\t'; break;
                    case 'b': ec='\b'; break;
                    case 'r': ec='\r'; break;
                    case 'f': ec='\f'; break;
                    case '\\': ec='\\'; break;
                    case '\'': ec='\''; break;
                    case '\"': ec='"'; break;
                    default:
                      ec=(char) Integer.parseInt(sval.substring(1),8);
                    };
                  } catch (NumberFormatException e) {
                    Debug.println("Can;t parse \""+cToken+
                                  "\" as a character literal.");
                  };
                  chr=ec;
                };
                list.addLast(new OPload(typesStk,Character.TYPE,
                                        new Character(chr)));
              };
              break;
            case '"':
              {
                String sval=cToken.toString().substring(1,cToken.length()-1);
                StringBuffer unescaped=new StringBuffer(sval.length());
                for(int i=0;i<sval.length();i++) {
                  char ec=sval.charAt(i);
                  if (ec=='\\') { // escape
                    ec=sval.charAt(++i);
                    switch (ec) {
                    case 'n': ec='\n'; break;
                    case 't': ec='\t'; break;
                    case 'b': ec='\b'; break;
                    case 'r': ec='\r'; break;
                    case 'f': ec='\f'; break;
                    case '\\': ec='\\'; break;
                    case '\'': ec='\''; break;
                    case '\"': ec='"'; break;
                    default:
                      int nval=0;
                      while ((i<sval.length()) && 
                             ((ec=sval.charAt(i))>='0') && (ec<='7')) {
                        nval=nval<<3+(ec-'0');
                        i++;
                      };
                      i--;
                      ec=(char)nval;
                    };
                  };
                  unescaped.append(ec);
                };
                list.addLast(new OPload(typesStk,
                                        unescaped.toString().getClass(),
                                        unescaped.toString()));
              };
              break;
            default: // function names 
              {
                if (cToken.toString().equals("true") )
                  list.addLast(new OPload(typesStk,
                                          Boolean.TYPE,Boolean.TRUE));
                else if (cToken.toString().equals("false"))
                  list.addLast(new OPload(typesStk,
                                          Boolean.TYPE,Boolean.FALSE));
                else {
                  // strip bracket from the name
                  cToken.setLength(cToken.length()-1);

                  // collect params
                  int ps=paramsStart.pop();
                  int np=typesStk.size()-ps;
                  Class[] params=new Class[np];
                  OP[] paramsOPs=new OP[np];
                  for(int i=np-1;i>=0;i--) {
                    params[i]=typesStk.pop();
                    paramsOPs[i]=(OP)paramOPs.pop();
                  };

                  // find method
                  Member m=null; 
                  try {
                    m=lib.getMember(cToken.toString(),params);
                  } catch (NoSuchMethodException exc) {
                    Debug.println("Can't find method \""+cToken+"\".");
                  };

                  // put "this" pointer in place
                  OP thisOP=(OP)paramOPs.pop();
                  if ((m.getModifiers() & 0x0008)==0) {
                    // insert loading of "this" pointer
                    OP op=new OPcall(typesStk,1,(new Object[0]).getClass());
                    if (thisOP==null) 
                      list.addFirst(op); 
                    else
                      list.addAfter(thisOP,op); 
                    thisOP=op;
                    paramOPs.push(op);
                    int classID=lib.getDynamicMethodClassID(m);
                    op=new OPload(typesStk,Integer.TYPE,new Integer(classID));
                    list.addAfter(thisOP,op); thisOP=op;
                    paramOPs.push(op);
                    op=new OPbinary(typesStk,paramOPs,19,list);
                    list.addAfter(thisOP,op); thisOP=op;
                    paramOPs.push(thisOP);
                  };
                
                  // restore params & param ops
                  for(int i=0;i<np;i++) {
                    typesStk.push(params[i]);
                    paramOPs.push(paramsOPs[i]);
                  };
                  list.addLast(new OPcall(typesStk,paramOPs,m,list,false));
                };
              };
            };
          };
        };
      } catch (IOException exc) {
        // IMPOSSIBLE
      };
      
      // remove TSB at return if present
      if (typesStk.peek()==TypesStack.tsb_class) {
        list.addLast(new OPunary(typesStk,8,OPunary.string_class,false)); 
      };

      int retID=typesStk.peekID();
      Class retType=typesStk.peek();

      list.addLast(new OPunary(typesStk,3)); // add the "return" instr.

      if (typesStk.size()!=0)
        Debug.println("Words left in stack when compiling.");

      // form name
      String name="evaluate";
      if (retID!=8) name=name+'_'+TypesStack.primitiveTypeNames[retID];

      boolean ok=true;

      for(int i=0;i<2;i++) {
        if (verbose) t.print(list);

        // make class
        ClassFile cf=(ClassFile)cf_orig.clone();

        // set return type
        cf.textData.patchAddress(retID_patchback,(byte)retID);

        cf.newMethod(eval_methods[retID],null);
        list.compile(cf);

        byte[] image=cf.getImage();

        dumpImage(cf);
        
        // load & execute
        CompiledExpression cexpr= 
          (CompiledExpression)(ImageLoader.load(image)).newInstance();
        
        Object res=cexpr.evaluate(thisPtrs);
        
        // compare results
        boolean localOK=((expRes==null) && (expRes==res)) ||
          (expRes.equals(res));
        
        if (verbose) {
          t.print(" == ");
          t.print(res);
          t.print("  ");
          if (localOK) t.print("ok."); else t.print("WRONG !!!");
          t.println("");
        };
        
        ok=ok && localOK;

        list.performCF();
      };
      
      if (ok) t.testOK(); else t.testFail();
      
      // rerun the failed test to get verbose output
      if (!(ok | verbose)) exprTest(expr, thisPtrs, lib, expRes,
                                    cf_orig, retID_patchback, eval_methods,
                                    t, true);


    } catch (Throwable thr) {
      Debug.reportThrowable(thr);
      t.testFail();
    };
  };

};



//  -------------- PatchableByteArrayOutputStream -------------------------- 
class PatchableByteArrayOutputStream extends java.io.ByteArrayOutputStream {
  public void patchAddress(int pos,byte address) {
    buf[pos]=address;
  };
  public void patchAddress(int pos,int address) {
    if (Debug.enabled)
      Debug.assert(address<=65535,"Jump destination is too far.");
    buf[pos  ]=(byte)((address >>> 8) & 0xFF);
    buf[pos+1]=(byte)((address >>> 0) & 0xFF);
  };
  public void patchAddressInt(int pos,int address) {
    buf[pos  ]=(byte)((address >>> 24) & 0xFF);
    buf[pos+1]=(byte)((address >>> 16) & 0xFF);
    buf[pos+2]=(byte)((address >>> 8) & 0xFF);
    buf[pos+3]=(byte)((address >>> 0) & 0xFF);
  };
};








