/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * $Id: TestSuite.java,v 1.24 2000/11/12 14:59:34 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,1999 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.tests;

import gnu.jel.*;
import gnu.jel.generated.EC;
import gnu.jel.generated.ParseException;
import gnu.jel.generated.TokenMgrError;
import java.io.PrintStream;

public class TestSuite {
  
  static int successes=0;
  static int failures=0;
  static boolean abort_at_error=false;// Set if You want to abort at a failing
  // test, works only for testExpression().
  
  public static void main(String[] args) {

    // Set UP Library
    Class[] staticLib=new Class[2];
    Class[] dotAllowedOn=new Class[1];
    try {
      staticLib[0]=Class.forName("java.lang.Math");
      VariableProvider tvp=new VariableProvider();
      staticLib[1]=tvp.getClass();
      dotAllowedOn[0]=Class.forName("java.lang.String");
//      staticLib[2]=Class.forName("gnu.jel.StringLib");
    } catch(ClassNotFoundException e) {
      System.out.println(e);
      System.exit(1);
    };
    Library lib=new Library(staticLib,null,dotAllowedOn);
    try {
      lib.markStateDependent("random",null);
    } catch (CompilationException e) {
      // Can't be also
    };

    PrintStream o=System.out;
    
    o.println(); o.println("----- TESTING ERROR REPORTING ------");
    o.println();

    testError("",null,lib,0,o);
    testError(" ",null,lib,1,o);
    testError("tru",null,lib,1,o);

    // two following tests may fail if the parser bug is not fixed
    // this fixing is done in the release script but the development
    // version always fails these tests
    boolean old_abort_at_error=abort_at_error;
    abort_at_error=false;
    testError("1=",null,lib,2,o);
    testError("1=2",null,lib,3,o);
    abort_at_error=old_abort_at_error;

    testError("1.0-+1.0",null,lib,5,o);
    testError("1.0&1.0",null,lib,4,o);
    testError("-",null,lib,1,o);
    testError("0x56+0xXX",null,lib,7,o);
    testError("Sin(x)",null,lib,5,o);
    testError("Sin(6)",null,lib,1,o);
    testError("sin(' ')",null,lib,1,o);
    //    testError("'a'+'b'",null,lib,4,o); // <- now perfectly legal 
    testError("1+sin(1,6)",null,lib,3,o);
    testError("2147483649L+2147483649",null,lib,13,o);
    testError("01234567+08+5",null,lib,11,o);
    testError("0.5+0#4",null,lib,6,o);
    testError("0.5+1",Integer.TYPE,lib,5,o);
    testError("0.5+(floatp)0.4D",null,lib,6,o);
    testError("0.5+(boolean)0.4D",null,lib,6,o);
    testError("23?1:2",null,lib,3,o);
    testError("true?\" \":' '",null,lib,5,o);
    testError("true?\" \":makeDoubleObject(1.0)",null,lib,5,o);
    
    o.println(); o.println("----- TESTING STATIC OPTIMIZATIONS ------");
    o.println("lines starting with <number>| denote constants folding passes.");
    o.println("Expression on each line is evaluated 20 times to provoke some JIT compilers.");
    o.println();
    
    testExpression("2*2",new Integer(4),null,null,lib,o);
    testExpression("2L*2L",new Long(4),null,null,lib,o);
    testExpression("2.0*2.0",new Double(4.0),null,null,lib,o);
    testExpression("2.0F*2.0F",new Float(4.0F),null,null,lib,o);
    testExpression("sin(1)",new Double(Math.sin(1.0)),null,null,lib,o);
    testExpression("pow(sin(1),2)+pow(cos(1),2)",new Double(1.0),null,null,
                   lib,o);
    testExpression("min(1+2*2,(1+2)*2)",new Integer(5),null,null,lib,o);
    testExpression("7+4-6",new Double(5.0),Double.TYPE,null,lib,o);
    testExpression("true&&false||false&&true||false",Boolean.FALSE,
                   null,null,lib,o);

    testExpression("(1<<2L==4)&&(-1>>5==-1)&&(-1>>>1==0x7FFFFFFF)",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("(-1>>>1==0x5FFFFFFF)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("(-1L>>>1L==0x7FFFFFFFFFFFFFFFL)",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("1+2>2==true",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("true?false:true?true:false",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("false?true:true?false:true",
                   Boolean.FALSE,null,null,lib,o);

    
    testExpression("(1==1)&&(max(~bool2int(1<=2),~bool2int(2>=3))!=-1)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("(-1==-1)&&(max(~(1<=2?1:0),~(2>=3?1:0))!=-1)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("!((!true)&&(!true))",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("!(!(false&&false&&false)&&!false)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("(!(5+1>5)?1:2)==2",
                   Boolean.TRUE,null,null,lib,o);

    testFullLogic("!(!(_a&&_b&&_c)&&!_d)",4,lib,o,false);
    
    testFullLogic("_a&&_b&&_c||_d",4,lib,o,false);

    testFullLogic("_a&&(_b&&(_c||_d))",4,lib,o,false);

    testFullLogic("_a&&((_b&&_c)||_d)",4,lib,o,false);
    
    testFullLogic("(_a&&_b)||!(_c&&_d)||_e",5,lib,o,false);

    testFullLogic("_a&&((_b&&(!(_c&&_d)||_e))||_f)",6,lib,o,false);

    testFullLogic("_a&&(!(_b&&(!(_c&&_d)||_e))||!_f)",6,lib,o,false);

    testFullLogic("_a&(!(_b&(!(_c&_d)|_e))|!_f)",6,lib,o,false);

    testFullLogic("_a?_b||_c||_d:_e&&_f",6,lib,o,false);

    testFullLogic("(_a==_b)&&(max(~bool2int(1<=2&&_c||_d),~bool2int(2>=3&&_e||_f))!=-1)",6,lib,o,false);

    testFullLogic("_a?_b:_c?_d:_e",5,lib,o,false);

    testExpression("\"aa\"+\"bb\"+\"cc\"",
                   "aabbcc",null,null,lib,o);

    testExpression("\"str\"+true+1+20L+6.0F+7.0D",
                   "strtrue1206.07.0",null,null,lib,o);

    testExpression("\"str\"+(2+3)","str5",null,null,lib,o);

    testExpression("((~(~((((1+2-2)*2/2)^55)^55)))|1&2)==1",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("((~(~((((1L+2L-2L)*2L/2L)^55L)^55L)))|1L&2L)==1L",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("((10/3)*3+10%3)==10",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("((10L/3)*3+10%3L)==10L",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("((1>5?0:10F)/3)*3+10F%3",
                   new Float(11.0F),null,null,lib,o);

    testExpression("((1>5?0:10D)/3)*3+10D%3",
                   new Double(11.0),null,null,lib,o);

    testExpression("round(((10-5)==5?(6.0-((8==8)?5.0:1))*2.0:1.0+"+
                   "2.0-1.0)/2.0)==1",
                   Boolean.TRUE,null,null,lib,o);


    // Tests if NaN (floating point "not a number") handling conforms to JLS
    testExpression("(1>NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1==NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(1!=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd>1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd<1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd>=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd<=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd==1)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(NaNd!=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1==NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(1!=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf>1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf<1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf>=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf<=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf==1)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(NaNf!=1)",Boolean.FALSE,null,null,lib,o);

    // In fact, next four tests are equivalent to the above ones,
    // but there was a problem with them and to solve it I had
    // to separate tests (making the above thing). I decided not
    // to remove the above since ... well... it makes testsuite runs
    // more impressive ;)))
    testExpression("(NaNd>1)||(NaNd<1)||(NaNd>=1)||(NaNd<=1)||"+
                   "(NaNd==1)||!(NaNd!=1)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("(1>NaNd)||(1<NaNd)||(1>=NaNd)||(1<=NaNd)||"+
                   "(1==NaNd)||!(1!=NaNd)",
                   Boolean.FALSE,null,null,lib,o);


    testExpression("(NaNf>1)||(NaNf<1)||(NaNf>=1)||(NaNf<=1)||"+
                   "(NaNf==1)||!(NaNf!=1)",
                   Boolean.FALSE,null,null,lib,o);

    testExpression("(1>NaNf)||(1<NaNf)||(1>=NaNf)||(1<=NaNf)||"+
                   "(1==NaNf)||!(1!=NaNf)",
                   Boolean.FALSE,null,null,lib,o);
    
    testExpression("\"aaaca\".indexOf('c')+1==4 && "+
                   "(1>2?\"cc\":\"aaaca\").indexOf('c')+1==4",
                   Boolean.TRUE,null,null,lib,o);

    testExpression("round((float)4)",
                   new Integer(4),null,null,lib,o);

    testExpression("(int)4.0",
                   new Integer(4),null,null,lib,o);

    
    testExpression("7+(int)4-(int)6.0+1-(int)round((double)((float)((long)1+0)+0)+0)",
                   new Integer(5),null,null,lib,o);
    
    testExpression("~0",new Integer(-1),null,null,lib,o);
		   
    testExpression("~0",new Integer(-1),null,null,lib,o);

    testExpression("(\"abbb\"+\"ccc\"+'d'+(1>2?\"e\":\"d\")).substring(1)",
                   "bbbcccdd",null,null,lib,o);

    testExpression("\"abbb\".substring(1).equals(\"bbb\")||false",
                   Boolean.TRUE,null,null,lib,o);
    //    testExpression("equals(substring(\"abbb\",1),\"bbb\")||false",
    //    Boolean.TRUE,null,null,lib,o);

    o.println(); 
    o.println("----- TESTING VIRTUAL METHODS (x=5.0D) ------");
    o.println();

    // Construct a new library
    Class[] dynamicLib=new Class[1];
    Object[] rtp=new Object[1];
    VariableProvider vp=new VariableProvider();
    Class oldmath=staticLib[0];
    staticLib=new Class[2];
    staticLib[0]=oldmath;
    // next line makes also static functions from VariablePrivider available
    staticLib[1]=vp.getClass();  
    
    vp.xvar=5.0;
    rtp[0]=vp;
    dynamicLib[0]=vp.getClass();
    lib=new Library(staticLib,dynamicLib);
    
    testExpression("sin(x/5)",new Double(Math.sin(1.0)),null,rtp,lib,o);
    testExpression("255+5+7+9-x",new Double(255+7+9),null,rtp,lib,o);
    testExpression("-x+255+5+7+9",new Double(255+7+9),null,rtp,lib,o);
    testExpression("-x+(255+5+7+9)",new Double(255+7+9),null,rtp,lib,o);
    testExpression("5*x-66",new Double(25-66),Double.TYPE,rtp,lib,o);
    testExpression("7+(int)4-(int)6.0+(int)x-(int)round((double)((float)((long)x+1)+2)+3)+6",
                   new Integer(5),null,rtp,lib,o);

    o.println(); o.println("----- VARIABLE AND ARRAY ACCESS ------");
    o.println();
    testExpression("5*xvar-66",new Double(25-66),Double.TYPE,rtp,lib,o);
    testExpression("5*arr[1]-66",new Double(25-66),Double.TYPE,rtp,lib,o);
    testExpression("5*(arrs[1]+arrs[2])-66",new Double(25-66),Double.TYPE,rtp,lib,o);
    testExpression("5*(arrs[1]+arrs[(int)round(arr[0]-arrs[2])+1])-66",new Double(25-66),Double.TYPE,rtp,lib,o);

    testExpression("arrDoubleJELObj1[0]+arrDoubleJELObj1[1]",
                   new Double(3.0), null, rtp, lib,o);

    o.println(); o.println("----- TESTING COMPILED-in EXCEPTIONS (x=5)------");
    o.println();
    
    testExpression("(1+6)/(2+2-4)",null,null,rtp,lib,o);
    testExpression("throw_arg_eq_4(6-2)",null,null,rtp,lib,o);

    o.println(); o.println("----- DOT OPERATOR ------");
    // Construct a new library
    Class[] dotLib=new Class[3];
    try {
      dotLib[0]=Class.forName("java.lang.String");
      dotLib[1]=Class.forName("java.lang.Double");
      dotLib[2]=Class.forName("gnu.jel.reflect.Double");
    } catch(ClassNotFoundException e) {
      System.out.println(e);
      System.exit(1);
    };
    
    lib=new Library(staticLib,dynamicLib,dotLib);

    testExpression("(\"abc\").length()", new Integer(3), null, rtp, lib,o);
    testExpression("\"abc\".length()", new Integer(3), null, rtp, lib,o);
    testExpression("\"abc\".endsWith(\"bc\")", Boolean.TRUE, null, rtp, lib,o);
    testExpression("\"abcdbc\".indexOf(\"bc\")", new Integer(1), null, rtp, lib,o);
    testExpression("\"abcdbc\".indexOf(\"abc\".substring(1),2)", new Integer(4), null, rtp, lib,o);

    testError("\"abc\".nomethod()",null,lib,7,o);
    testError("\"abc\".length(666)",null,lib,7,o);
    testError("2.0.doubleValue()",null,lib,5,o);
    
    testExpression("intObj+1", new Integer(556), null, rtp, lib,o);
    testExpression("intObj", new Integer(555), null, rtp, lib,o);
    testExpression("arr[intObj-554]", new Double(5.0), null, rtp, lib,o);
    testExpression("arr[max(intObj-554,0)]", new Double(5.0), null, rtp, lib,o);
    testExpression("arr[max(intObj,0)-554]", new Double(5.0), null, rtp, lib,o);

    testExpression("arr[byteObj]", new Double(6.0), null, rtp, lib,o);
    testExpression("byteObj+1.0", new Double(3.0), null, rtp, lib,o);

    testExpression("arrDoubleJELObj1[0].getValue()+"+
                   "arrDoubleJELObj1[1].getValue()",
                   new Double(3.0), null, rtp, lib,o);


    o.println(); o.println("----- DYNAMIC VARIABLES ------");
    lib=new Library(staticLib,dynamicLib,dotLib,vp);
    vp.addProperty("p1","p1value");
    vp.addProperty("p1.s1","p1s1value");
    vp.addProperty("p1.s2","p1s2value");
    vp.addProperty("p1.d1",VariableProvider.makeJELDoubleObject(1.0));
    vp.addProperty("p1.s2.ss1","p1s2ss1value");
    vp.addProperty("p1.b1t",VariableProvider.makeJELBooleanObject(true));
    vp.addProperty("p1.b1f",VariableProvider.makeJELBooleanObject(false));
    testExpression("p1", "p1value", null, rtp, lib,o);
    testExpression("p1.s1", "p1s1value", null, rtp, lib,o);
    testExpression("p1.s2", "p1s2value", null, rtp, lib,o);
    testExpression("p1.s2.ss1","p1s2ss1value", null, rtp, lib,o);
    testExpression("p1.s2.length()", new Integer(9), null, rtp, lib,o);
    testExpression("p1+(p1.d1+p1.s2.length()+1)", "p1value11.0", null, rtp, lib,o);
    testExpression("round(p1.d1)", new Long(1), null, rtp, lib,o);
    testExpression("p1.d1>0", new Boolean(true), null, rtp, lib,o);
    testExpression("p1.d1>0?p1.d1:3.0", new Double(1.0), null, rtp, lib,o);
    testExpression("p1.b1t", new Boolean(true), null, rtp, lib,o);
    testExpression("(boolean)p1.b1t", new Boolean(true), null, rtp, lib,o);
    testExpression("p1.b1t?1:0", new Byte((byte)1), null, rtp, lib,o);

    testExpression("aarr[1][0]", new Double(3.0), null, rtp, lib,o);

    testExpression("aarrDouble[1][0].doubleValue()", 
                   new Double(3.0), null, rtp, lib,o);

    testExpression("aarrDouble[1][0]", 
                   new Double(3.0), null, rtp, lib,o);

    testExpression("\"\"+aarr[1][0]","3.0", null, rtp, lib,o);
    testExpression("\"\"+aarrDouble[1][0]","3.0", null, rtp, lib,o);
    testExpression("\"\"+aarrDouble[1][0].doubleValue",
                   "3.0", null, rtp, lib,o);

    testExpression("\"\"+\"\"+aarr[1][0]","3.0", null, rtp, lib,o);
    testExpression("\"a\"+\"b\"+aarr[1][0]+\"c\"","ab3.0c", null, rtp, lib,o);


    // Test case for a bug submitted by Dave Ekhaus
    {
      for(int i=0;i<8;i++) {
        Class[] newDynamicLib=new Class[dynamicLib.length+1];
        Object[] newRTP=new Object[rtp.length+1];
        System.arraycopy(dynamicLib,0,newDynamicLib,0,dynamicLib.length);
        System.arraycopy(rtp,0,newRTP,0,rtp.length);
        switch(i) {
        case 0:
          newRTP[newRTP.length-1]=VariableProvider.makeJELBooleanObject(false);
          break;
        case 1:
          newRTP[newRTP.length-1]=VariableProvider.makeJELByteObject((byte)55);
          break;
        case 2:
          newRTP[newRTP.length-1]=VariableProvider.makeJELCharacterObject(' ');
          break;
        case 3:
          newRTP[newRTP.length-1]=
            VariableProvider.makeJELShortObject((short)55);
          break;
        case 4:
          newRTP[newRTP.length-1]=VariableProvider.makeJELIntegerObject(55);
          break;
        case 5:
          newRTP[newRTP.length-1]=VariableProvider.makeJELLongObject(55);
          break;
        case 6:
          newRTP[newRTP.length-1]=VariableProvider.makeJELFloatObject(55);
          break;
        case 7:
          newRTP[newRTP.length-1]=VariableProvider.makeJELDoubleObject(55);
          break;
        default:
        };
        newDynamicLib[newDynamicLib.length-1]=
          newRTP[newRTP.length-1].getClass();
        
        Library lib1=new Library(staticLib,newDynamicLib,new Class[0],vp);
        testExpression("aMethod", new Integer(1),null,newRTP,lib1,o);
      };
    };

    for(int k=0;k<2;k++) {
      o.println(); 
      o.println("----- THOROUGNLY TEST UNARY OPERATIONS  ------");

      String[][] prefixes={{"val","val","val"},{"arr","arr","arr"}};
      String[][] suffixes={{"","Obj","JELObj"},{"[0]","Obj[0]","JELObj[0]"}};
      
      testUnaryPrimitive(0,6,lib,rtp,-1,o,prefixes[k],suffixes[k]); // -
      testUnaryPrimitive(1,5,lib,rtp,0xFFFFFFFFFFFFFFFEL,o,
                         prefixes[k],suffixes[k]); // ~
      testUnaryPrimitive(2,1,lib,rtp,0,o,prefixes[k],suffixes[k]); // !

      o.println(); 
      o.println("----- THOROUGNLY TEST BINARY OPERATIONS  ------");


      testBinaryPrimitive(0,45,lib,rtp,2,o,prefixes[k],suffixes[k]); // +
      testBinaryPrimitive(1,45,lib,rtp,0,o,prefixes[k],suffixes[k]); // -
      testBinaryPrimitive(2,45,lib,rtp,1,o,prefixes[k],suffixes[k]); // *
      testBinaryPrimitive(3,45,lib,rtp,1,o,prefixes[k],suffixes[k]); // /
      testBinaryPrimitive(4,45,lib,rtp,0,o,prefixes[k],suffixes[k]); // %
      testBinaryPrimitive(5,26,lib,rtp,1,o,prefixes[k],suffixes[k]); // &
      testBinaryPrimitive(6,26,lib,rtp,1,o,prefixes[k],suffixes[k]); // |
      testBinaryPrimitive(7,26,lib,rtp,0,o,prefixes[k],suffixes[k]); // ^
      testBinaryPrimitive(8,46,lib,rtp,1,o,prefixes[k],suffixes[k]); // ==
      testBinaryPrimitive(9,46,lib,rtp,0,o,prefixes[k],suffixes[k]); // !=
      testBinaryPrimitive(10,45,lib,rtp,0,o,prefixes[k],suffixes[k]); // <
      testBinaryPrimitive(11,45,lib,rtp,1,o,prefixes[k],suffixes[k]); // >=
      testBinaryPrimitive(12,45,lib,rtp,0,o,prefixes[k],suffixes[k]); // >
      testBinaryPrimitive(13,45,lib,rtp,1,o,prefixes[k],suffixes[k]); // <=
      testBinaryPrimitive(14,25,lib,rtp,2,o,prefixes[k],suffixes[k]); // <<
      testBinaryPrimitive(15,25,lib,rtp,0,o,prefixes[k],suffixes[k]); // >>
      testBinaryPrimitive(16,25,lib,rtp,0,o,prefixes[k],suffixes[k]); // >>>
      testBinaryPrimitive(17,1,lib,rtp,1,o,prefixes[k],suffixes[k]); // &&
      testBinaryPrimitive(18,1,lib,rtp,1,o,prefixes[k],suffixes[k]); // ||

    };


    o.println(); o.println("^^^^^^^^^^^^ SCORE success/failure = "+
                           successes+"/"+failures+
                           " ^^^^^^^^^^^^" );
  };


  // Tests a given binary operation on all primitive types
  private static void testUnaryPrimitive(int code,int npbc,
                                         Library lib, Object[] context,
                                         long resVal, PrintStream o,
                                         String[] prefixes,
                                         String[] suffixes) {
    String[] typeNames={"Boolean","Byte","Character","Short","Integer",
                        "Long","Float","Double"};

    String[] opSymbols={"-","~","!"};
    
    Object[] typeConstants={
      new java.lang.Boolean(true),
      new java.lang.Byte((byte)1),
      new java.lang.Character((char)1),
      new java.lang.Short((short)1),
      new java.lang.Integer(1),
      new java.lang.Long(1),
      new java.lang.Float((float)1.0),
      new java.lang.Double(1.0)};

    int npbcActual=0;

    for(int i=0;i<8;i++) {
        // determine the result type independently
        int resID=-1;
        try {
          OPlist list=new OPlist();
          TypesStack typeStk=new TypesStack();
          OP o1=new OPload(typeStk,typeConstants[i]);
          list.addLast(o1);

          list.addLast(new OPunary(typeStk,code));
          resID=typeStk.peekID();
          npbcActual++;
        } catch (CompilationException exc) {
        };
        Object res=null;
        if (resID>=0) { // result exists test it
          // construct the resulting object
          switch (resID) {
          case 0:
            res=new java.lang.Boolean(resVal>0?true:false);
            break;
          case 1:
            res=new java.lang.Byte((byte)resVal);
            break;
          case 2:
            res=new java.lang.Character((char)resVal);
            break;
          case 3:
            res=new java.lang.Short((short)resVal);
            break;
          case 4:
            res=new java.lang.Integer((int)resVal);
            break;
          case 5:
            res=new java.lang.Long(resVal);
            break;
          case 6:
            res=new java.lang.Float((float)resVal);
            break;
          case 7:
            res=new java.lang.Double((double)resVal);
            break;
          default:
            o.print("*** : ");            
            o.println("The result of unary operation \""+opSymbols[code]+
                      "\" on "+TypesStack.specialTypes[i]+
                      " is not primitive type ID="+resID);
            FAIL(o);
          };
        };

        for(int k=0;k<prefixes.length;k++) {
          for(int m=k;m<prefixes.length;m++) {
            String op1=prefixes[k]+typeNames[i]+suffixes[k];
            String expr=opSymbols[code]+op1;
            if (res!=null)
              testExpression(expr,res,null,context,lib,o);
            else
              testError(expr,null,lib,1,o);   
          };
        };
        
      };
    
    o.print("*=*=*= : the total number of successful operations "+npbcActual);
    if (npbcActual==npbc) {
      o.println(".");
      OK(o);
    } else {
      o.println(" must be "+npbc);
      FAIL(o);
    };
  };

  
  // Tests a given binary operation on all primitive types
  private static void testBinaryPrimitive(int code,int npbc,
                                          Library lib, Object[] context,
                                          int resVal, PrintStream o,
                                         String[] prefixes,
                                         String[] suffixes) {
    String[] typeNames={"Boolean","Byte","Character","Short","Integer",
                        "Long","Float","Double"};

    String[] opSymbols=(String[])TableKeeper.getTable("binOpSymbols");
    
    Object[] typeConstants={
      new java.lang.Boolean(true),
      new java.lang.Byte((byte)1),
      new java.lang.Character((char)1),
      new java.lang.Short((short)1),
      new java.lang.Integer(1),
      new java.lang.Long(1),
      new java.lang.Float((float)1.0),
      new java.lang.Double(1.0)};

    int npbcActual=0;

    for(int i=0;i<8;i++)
      for(int j=0;j<8;j++) {
        // determine the result type independently
        int resID=-1;
        try {
          OPlist list=new OPlist();
          TypesStack typeStk=new TypesStack();
          java.util.Stack paramOPs=new java.util.Stack();
          OP o1=new OPload(typeStk,typeConstants[i]);
          list.addLast(o1);
          paramOPs.push(o1);

          OP o2=new OPload(typeStk,typeConstants[j]);
          list.addLast(o2);
          paramOPs.push(o2);

          list.addLast(new OPbinary(typeStk,paramOPs,code,list));
          resID=typeStk.peekID();
          npbcActual++;
        } catch (CompilationException exc) {
        };
        Object res=null;
        if (resID>=0) { // result exists test it
          // construct the resulting object
          switch (resID) {
          case 0:
            res=new java.lang.Boolean(resVal>0?true:false);
            break;
          case 1:
            res=new java.lang.Byte((byte)resVal);
            break;
          case 2:
            res=new java.lang.Character((char)resVal);
            break;
          case 3:
            res=new java.lang.Short((short)resVal);
            break;
          case 4:
            res=new java.lang.Integer(resVal);
            break;
          case 5:
            res=new java.lang.Long(resVal);
            break;
          case 6:
            res=new java.lang.Float(resVal);
            break;
          case 7:
            res=new java.lang.Double(resVal);
            break;
          default:
            o.print("*** : ");            
            o.println("The result of binary operation \""+opSymbols[code]+
                      "\" on "+TypesStack.specialTypes[i]+" and "+
                      TypesStack.specialTypes[j]+
                      " is not primitive type ID="+resID);
            FAIL(o);
          };
        };

        for(int k=0;k<prefixes.length;k++) {
          for(int m=k;m<prefixes.length;m++) {
            String op1=prefixes[k]+typeNames[i]+suffixes[k];
            String expr=op1+
              opSymbols[code]+prefixes[m]+typeNames[j]+suffixes[m];
            if (res!=null)
              testExpression(expr,res,null,context,lib,o);
            else
              testError(expr,null,lib,op1.length()+1,o);              
          };
        };
        
      };
    
    o.print("*=*=*= : the total number of successful operations "+npbcActual);
    if (npbcActual==npbc) {
      o.println(".");
      OK(o);
    } else {
      o.println(" must be "+npbc);
      FAIL(o);
    };
  };

  // Tests evaluation of logical expressions
  // The input is an expression of the form "a&b|c&d"
  // where there are n<=32 free parameters _a,_b,_c,_d (starting from a_ in 
  // alphabetical order) the expression should involve
  // only stateless functions otherwise this test has no sense.
  //
  // This function will evaluate the expression 2^n times for all possible
  // combinations of parameters and compare results of "interpreted" vs 
  // "compiled" evaluation. If in all 2^n cases results will coincide the test
  // is marked as PASSED.
  // This function does not analyze syntax of expression so be sure not to have
  // underscores ("_") in the names of functions.
  private static void testFullLogic(String expr,int bits,Library lib, 
                                    PrintStream o,boolean showcases) {
    int cases=1<<bits;
    o.print("*** : FULL LOGIC TEST \""); o.print(expr); 
    o.println("\" . ( "+cases+" cases ).");    
    
    boolean vars[]=new boolean[bits];
    boolean testOK=true;
    for (int ccase=0;((ccase<cases)&&testOK);ccase++) {
      for(int i=0;i<bits;i++) vars[i]=((ccase>>>i & 0x00000001)>0?true:false);
      StringBuffer cexpr=new StringBuffer();
      for (int i=0;i<expr.length();i++) {
        char currchar=expr.charAt(i);
        if (currchar=='_') {
          currchar=expr.charAt(++i);
          int varnum=currchar-'a';
          if (vars[varnum]) cexpr.append("true "); else  cexpr.append("false");
        } else cexpr.append(currchar);
      };

      // Now we need to calculate cexpr
      
      // First parse it
      OPlist op=null;
      try {
        op=OpenEvaluator.parse(cexpr.toString(),lib,null);
      } catch (CompilationException ce) {
        o.print("--- COMPILATION ERROR :");
        o.println(ce.getMessage());
        o.print("                       ");
        o.println(cexpr.toString());
        int column=ce.getColumn(); // Column, where error was found
        for(int i=0;i<column+23-1;i++) System.err.print(' ');
        o.println('^');
        o.println("Unexpected syntax error on supposingly correct"+
                  " expression.");
        FAIL(o);
        return;
      };
      
      // Make optimization iterations
      Object result=null;      

      for(int iteration=0;iteration<2;iteration++) {
        Object result1=null;      
        try {
          CompiledExpression expr_c=OpenEvaluator.compile(op);

          // Execute several times to enable JIT compilation.
          // Some JITs compile methods if they are run more than once
          for(int acounter=0;acounter<20;acounter++) {
            result1=expr_c.evaluate(null);
          };
        } catch (Throwable e) {
          o.println(cexpr.toString());
          o.println("Exception emerged during compilation/evaluation.");
          o.print("      ");e.printStackTrace();
          FAIL(o);
          return;
        };
	
        if (result!=null) {
          if (!result.equals(result1)) {
            o.println(cexpr.toString());
            o.println("Expression gave inconsistent result "+result1+
                      " ( previous result was "+result+" ).");
            o.println(op.toString());
            FAIL(o);
            return;
          };
        };
        result=result1;

        if (iteration==0) op.performCF();
      };
      
      if (showcases) {
        o.print(cexpr.toString()); 
        o.print(" == "); 
        o.println(result.toString());
      };
    };
    OK(o);
  };

  private static void testExpression(String expr, Object tobe, Class fixType,
                                     Object[] runtimeParameters,
                                     Library lib,   PrintStream o ) {
    o.print("*** : \""); o.print(expr); 
    if (tobe != null) {
      o.print("\" = "); o.println(tobe);
    } else {
      o.println("\"   Should throw an exception at run time.");
    };
    OPlist op=null;
    try {
      op=OpenEvaluator.parse(expr,lib,fixType);
    } catch (CompilationException ce) {
      o.print("--- COMPILATION ERROR :");
      o.println(ce.getMessage());
      o.print("                       ");
      o.println(expr);
      int column=ce.getColumn(); // Column, where error was found
      for(int i=0;i<column+23-1;i++) System.err.print(' ');
      o.println('^');
      o.println("Unexpected syntax error on supposingly correct expression.");
      FAIL(o);
      return;
    };
    
    boolean testok=true;
    
    for(int iteration=0;iteration<2;iteration++) {
      String message=""+iteration+" |"+op.toString();
      o.print(message);
      for (int k=message.length();k<59;k++) o.print(' ');

      Object result=null;
      try {
        CompiledExpression expr_c=OpenEvaluator.compile(op);
	
        // Execute several times to enable JIT compilation.
        // Some JITs compile methods if they are run more than once
        for(int acounter=0;acounter<20;acounter++) {
          result=expr_c.evaluate(runtimeParameters);
        };
      } catch (Throwable e) {
        if (tobe==null) {
          o.println("EXPECTED EXCEPTION.");
          o.print("      ");o.println(e.getMessage());
        } else {
          o.println("Exception emerged during compilation/evaluation.");
          o.print("      ");e.printStackTrace();
        };
        testok=(tobe==null);
      };
      
      if (tobe!=null) {
        if (result!=null) {
          o.print(" ="); o.println(result);
          testok=testok && (result.equals(tobe));
        } else {
          o.println("NO RESULT");
          testok=false;
        };
      } else {
        testok=(result==null);
        if (result!=null) o.println(" ="+result.toString());
      };
      
      if (iteration==0) op.performCF();
    };
    
    if (testok) OK(o); else FAIL(o);
    
  };
  
  private static void testError(String expr,Class fixType, Library lib, 
                                int errcol, PrintStream o) {
    o.print("*** : \""); o.print(expr); o.println('"');

    CompilationException ce=null;
    try {
      OPlist op=OpenEvaluator.parse(expr,lib,fixType);
    } catch (CompilationException e) {
      ce=e;
    };
    if (ce==null) {
      o.println("No error detected."); 
      FAIL(o);
    } else {
      o.print("       ");
      int column=ce.getColumn(); // Column, where error was found
      for(int i=0;i<column-1;i++) o.print(' ');
      o.println('^');
      
      o.print("MESSAGE: "); o.println(ce.getMessage());
      
      if (ce.getColumn()!=errcol) {
        o.print("Error detected at column "); o.print(ce.getColumn());
        o.print(" while it should be at "); o.print(errcol); o.println(" .");
        FAIL(o);
      } else OK(o);

    };

  };

  private static void FAIL(PrintStream o) {
    failures++;
    o.println(" TEST FAILED !!!"); //o.println(); 
    if (abort_at_error) System.exit(1);
  };

  private static void OK(PrintStream o) {
    successes++;
    o.println(" TEST PASSED."); //o.println();
  };
  
};

class OpenEvaluator extends Evaluator {
  
  public static OPlist parse(String expression, Library lib,
                             Class resultType) throws CompilationException {
	return Evaluator.parse(expression,lib,resultType);
  };

  public static CompiledExpression compile(OPlist list) {
	byte[] image=Evaluator.getImage(list);
    try {
      java.io.FileOutputStream fos=new java.io.FileOutputStream("dump.class");
      fos.write(image);
      fos.close();
    } catch (java.io.IOException exc) {
      gnu.jel.debug.Debug.reportThrowable(exc);
    };
	try {
      return (CompiledExpression)(ImageLoader.load(image)).newInstance();
	} catch (Exception exc) {
      return null;
	};
  };
  
};
