/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* $Id: $
* (c) 1998 -- 2007 by Konstantin L. Metlov
*/
package gnu.jel;
import junit.framework.TestCase;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import java.lang.reflect.Constructor;
import java.util.Stack;
public class ClassFileExprTest extends TestCase {
public ClassFileExprTest(String name) {
super(name);
}
ClassFile cf;
Library lib;
Object[] dynalib;
byte[] image;
LocalMethod[] eval_methods;
ClassFile cf_orig;
int retID_patchback;
public void setUp() throws Exception {
LocalField[] lf=new LocalField[1];
// private Object[] e;
lf[0]=new LocalField(0x0002,(new Object[0]).getClass(),"e",null);
// cf = new ClassFile(0x0001,"dump",(new Object()).getClass(),null,lf);
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,null,null,null);
dynalib=new Object[1];
dynalib[0]=new Double(100.0);
image=null;
final int numPrimitives=10; // up to Void
eval_methods=new LocalMethod[numPrimitives];
cf_orig=null;
retID_patchback=0;
// prepare eval methods
Class[] paramsE=new Class[1];
paramsE[0]=(new Object[0]).getClass();
for(int i=0;i<numPrimitives-1;i++) {
String name="evaluate";
Class cls=OP.specialTypes[i];
if (i!=8)
name=name+'_'+cls;
else
cls=(new Object()).getClass();
eval_methods[i]=new LocalMethod(0x0001,cls,name,paramsE,null);
};
// hand-compile a class template
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.noteStk(-1,11); // not important what, it must be a reference
Constructor supInit=cmplExpr.getConstructor(new Class[0]);
cf.code(0xb7); //| invokespecial
cf.writeShort(cf.getIndex(supInit,11)); //| super();
cf.noteStk(11,-1);
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.tsize;
cf.code(8); // type placeholder
cf.noteStk(-1,4); // note "int"
cf.code(0xAC); //| ireturn
cf.noteStk(4,-1); // rm "int"
cf_orig=(ClassFile)cf.clone();
cf.newMethod(eval_methods[8],null);
cf.code(0x01); //| aconst_null
cf.noteStk(-1,11); // not important what, it must be a reference
cf.code(0xB0); //| areturn
cf.noteStk(11,-1);
image=cf.getImage();
}
public void tearDown() throws Exception {
}
public void testExecTemplate() throws Throwable {
CompiledExpression expr=
(CompiledExpression)(ImageLoader.load(image)).newInstance();
boolean ok=((expr.getType()==8) && (expr.evaluate(null)==null));
assertTrue(ok);
}
public void testExpr1() throws Throwable {
exprMiniComp("1 (I)",dynalib,lib,new Integer(1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr2() throws Throwable {
exprMiniComp("1 (I)",dynalib,lib,new Integer(1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr3() throws Throwable {
exprMiniComp("1 -- (I)",dynalib,lib,new Integer(-1),
cf_orig,retID_patchback,eval_methods,false);
};
// Passing constants of primitive types.
// this also tests shortcut commands for loading some integer
// and floating point constants
public void testExpr4() throws Throwable {
exprMiniComp("1 --",dynalib,lib,new Integer((byte)-1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr5() throws Throwable {
exprMiniComp("1L --",dynalib,lib,new Long(-1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr6() throws Throwable {
for(byte i=0;i<=6;i++) {
exprMiniComp(String.valueOf(i),dynalib,lib,new Byte(i),
cf_orig,retID_patchback,eval_methods,false);
exprMiniComp(String.valueOf(i)+'L',dynalib,lib,new Long(i),
cf_orig,retID_patchback,eval_methods,false);
};
};
public void testExpr7() throws Throwable {
for(byte i=0;i<=3;i++) {
exprMiniComp(String.valueOf(i)+".0F",dynalib,lib,new Float(i),
cf_orig,retID_patchback,eval_methods,false);
exprMiniComp(String.valueOf(i)+".0",dynalib,lib,new Double(i),
cf_orig,retID_patchback,eval_methods,false);
};
};
public void testExpr8() throws Throwable {
exprMiniComp("true",dynalib,lib,Boolean.TRUE,
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr9() throws Throwable {
exprMiniComp("false",dynalib,lib,Boolean.FALSE,
cf_orig,retID_patchback,eval_methods,false);
};
// this tests immediate byte constants loading (1 byte in the code)
// and loading through the CP.
public void testExpr10() throws Throwable {
for(int i=126;i<=128;i++) {
exprMiniComp(String.valueOf(i)+" (I)",dynalib,lib,new Integer(i),
cf_orig,retID_patchback,eval_methods,false);
exprMiniComp(String.valueOf(i)+" -- (I)",dynalib,lib,new Integer(-i),
cf_orig,retID_patchback,eval_methods,false);
};
};
public void testExpr11() throws Throwable {
exprMiniComp("( 1 , 2 , min)",dynalib,lib,new Integer(1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr12() throws Throwable {
exprMiniComp("( ( E) , sin)",dynalib,lib,new Double(Math.sin(Math.E)),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr13() throws Throwable {
exprMiniComp("2 , 2 , *",dynalib,lib,new Integer(4),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr14() throws Throwable {
exprMiniComp("3 , 2 , * , 1 , -",dynalib,lib,new Integer(5),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr15() throws Throwable {
exprMiniComp("3 , 2 , * , 1 , - , 3 , - , 2 , *",dynalib,lib,
new Integer(4),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr16() throws Throwable {
exprMiniComp("3 , 2 , * , 1 , - , 3 , - , 2 , * , 4 , ==",dynalib,lib,
Boolean.TRUE,
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr17() throws Throwable {
exprMiniComp("\"a\" , \"b\" , + , 4 , + , true , +",dynalib,lib,
"ab4true",
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr18() throws Throwable {
exprMiniComp("2 , 3 , > , 3 , 2 , >= , ||",dynalib,lib,
Boolean.TRUE,
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr19() throws Throwable {
exprMiniComp("( isNaN)",dynalib,lib,
Boolean.FALSE,
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr20() throws Throwable {
exprMiniComp("1 , ( doubleValue) , + , 101 , ==",dynalib,lib,
Boolean.TRUE,
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr21() throws Throwable {
exprMiniComp("( true ? 1 : 2 ) (I)",dynalib,lib,
new Integer(1),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr22() throws Throwable {
exprMiniComp("( false ? 1 : 2 ) (I)",dynalib,lib,
new Integer(2),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr23() throws Throwable {
exprMiniComp("( false ? 1 : 2 , 2 , + ) (I)",dynalib,lib,
new Integer(4),
cf_orig,retID_patchback,eval_methods,false);
};
public void testExpr24() throws Throwable {
exprMiniComp("( true ? ( 1 , 3 , + , 5 , min) : 2 ) (I)",dynalib,lib,
new Integer(4),
cf_orig,retID_patchback,eval_methods,false);
};
private static void exprMiniComp(String expr, Object[] thisPtrs, Library lib,
Object expRes, ClassFile cf_orig,
int retID_patchback,
LocalMethod[] eval_methods,
boolean verbose) throws Throwable {
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=OP.typeIDObject(expRes);
if (id<8) {
testTitle.append(expRes);
testTitle.append(id>9?'L':"ZBCSIJFDLV".charAt(id));
} else if (expRes instanceof String) {
testTitle.append('"');
testTitle.append(expRes);
testTitle.append('"');
} else testTitle.append(expRes);
}
// this.setName(testTitle.toString());
Stack paramOPs=new Stack();
IntegerStack paramsStart=new IntegerStack();
Stack oldLists=new Stack();
IntegerStack branchStack=new IntegerStack();
OP cop;
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 '~':
paramOPs.push(new OPunary(paramOPs,1));
break;
case ',':
// paramOPs.push(list.getLast());
break;
case '(': // can be type conversion or function
if (cToken.length()==1) { // function
paramsStart.push(paramOPs.size());
} else if (cToken.length()==3) { // type conversion
char ttype=cToken.charAt(1);
int tid;
char[] primitiveCodes= {'Z','B','C','S',
'I','J','F','D','L','V','L',
'L'};
for(tid=0;(tid<primitiveCodes.length) &&
(ttype!=primitiveCodes[tid]);tid++);
paramOPs.push(new OPunary(paramOPs,tid,null,true));
} else System.err.println("Wrong bracketed token \""+cToken+"\".");
break;
case '?':
break;
case ':':
break;
case ')':
paramOPs.push(new OPcondtnl(paramOPs));
break;
case '+':
paramOPs.push(new OPbinary(paramOPs,0));
break;
case '-':
if (cToken.length()==1) {
paramOPs.push(new OPbinary(paramOPs,1));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='-'))
paramOPs.push(new OPunary(paramOPs,0));
else System.err.println("Wrong token \""+cTok+"\".");
break;
case '*':
paramOPs.push(new OPbinary(paramOPs,2));
break;
case '/':
paramOPs.push(new OPbinary(paramOPs,3));
break;
case '%':
paramOPs.push(new OPbinary(paramOPs,4));
break;
case '^':
paramOPs.push(new OPbinary(paramOPs,7));
break;
case '=':
if (cToken.charAt(1)=='=')
paramOPs.push(new OPbinary(paramOPs,8));
break;
case '!':
if (cToken.length()==1) {
paramOPs.push(new OPunary(paramOPs,2));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
paramOPs.push(new OPbinary(paramOPs,9));
} else System.err.println("Wrong ! token \""+cTok+"\".");
break;
case '<':
if (cToken.length()==1) {
paramOPs.push(new OPbinary(paramOPs,10));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
paramOPs.push(new OPbinary(paramOPs,13));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='<')) {
paramOPs.push(new OPbinary(paramOPs,14));
} else System.err.println("Wrong < token \""+cTok+"\".");
break;
case '>':
if (cToken.length()==1) {
paramOPs.push(new OPbinary(paramOPs,11));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='=')) {
paramOPs.push(new OPbinary(paramOPs,12));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='>')) {
paramOPs.push(new OPbinary(paramOPs,15));
} else if ((cToken.length()==3) && (cToken.charAt(2)=='>')) {
paramOPs.push(new OPbinary(paramOPs,16));
} else System.err.println("Wrong > token \""+cTok+"\".");
break;
case '&':
if (cToken.length()==1) {
paramOPs.push(new OPbinary(paramOPs,5));
} else if ((cToken.length()==3) && (cToken.charAt(1)=='&')) {
paramOPs.push(new OPbinary(paramOPs,17));
} else System.err.println("Wrong & token \""+cTok+"\".");
break;
case '|':
if (cToken.length()==1) {
paramOPs.push(new OPbinary(paramOPs,6));
} else if ((cToken.length()==2) && (cToken.charAt(1)=='|')) {
paramOPs.push(new OPbinary(paramOPs,18));
} else System.err.println("Wrong | token \""+cTok+"\".");
break;
case '[':
if ((cToken.length()==3) && (cToken.charAt(1)==']'))
paramOPs.push(new OPbinary(paramOPs,19));
else System.err.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) {
System.err.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;
};
paramOPs.push(new OPload(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) {
System.err.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
System.err.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;
};
paramOPs.push(new OPload(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) {
System.err.println("Can;t parse \""+cToken+
"\" as a character literal.");
};
chr=ec;
};
paramOPs.push(new OPload(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);
};
paramOPs.push(new OPload(unescaped.toString()));
};
break;
default: // function names
{
if (cToken.toString().equals("true") )
paramOPs.push(new OPload(Boolean.TRUE));
else if (cToken.toString().equals("false"))
paramOPs.push(new OPload(Boolean.FALSE));
else {
// strip bracket from the name
cToken.setLength(cToken.length()-1);
// collect params
int ps=paramsStart.pop();
int np=paramOPs.size()-ps;
Class[] params=new Class[np];
OP[] paramsOPs=new OP[np];
for(int i=np-1;i>=0;i--) {
paramsOPs[i]=(OP)paramOPs.pop();
params[i]=paramsOPs[i].resType;
};
// find method
Member m=null;
try {
m=lib.getMember(null,cToken.toString(),params);
} catch (CompilationException exc) {
System.err.println("Can't find method \""+cToken+"\".");
};
// put "this" pointer in place
if ((m.getModifiers() & 0x0008)==0) {
// insert loading of "this" pointer
paramOPs.push(new OPcall(1,
(new Object[0]).getClass()));
int classID=lib.getDynamicMethodClassID(m);
paramOPs.push(new OPload(new Integer(classID)));
paramOPs.push(new OPbinary(paramOPs,19));
};
// restore params & param ops
for(int i=0;i<np;i++) {
paramOPs.push(paramsOPs[i]);
};
paramOPs.push(new OPcall(paramOPs,m,false));
};
};
};
};
};
// remove TSB at return if present
if (((OP)paramOPs.peek()).resID==10) {
paramOPs.push(new OPunary(paramOPs,11,null,false));
};
OP rop=(OP)paramOPs.peek();
int retID=rop.resID>9?8:rop.resID; // compute base type (actually
// not needed since RET does it alr.)
Class retType=rop.resType;
// add the "return" instruction
paramOPs.push(new OPunary(paramOPs,3));
if (paramOPs.size()!=1)
System.err.println("Extra paramOPs left in stack when compiling.");
OP program=(OP) paramOPs.pop();
// form name
String name="evaluate";
if (retID!=8) name=name+'_'+retType;
boolean ok=true;
for(int i=0;i<2;i++) {
if (verbose) System.out.print(toStr(program));
// make class
ClassFile cf=(ClassFile)cf_orig.clone();
// set return type
int otsize=cf.tsize;
cf.tsize=retID_patchback;
cf.write(retID);
cf.tsize=otsize;
cf.newMethod(eval_methods[retID],null);
program.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) {
System.out.print(" == ");
System.out.print(res);
System.out.print(" ");
if (localOK)
System.out.print("ok.");
else
System.out.print("WRONG !!!");
System.out.println("");
};
ok=ok && localOK;
try {
program=new OPload(program,program.eval());
} catch (Exception exc) {
};
};
assertTrue(testTitle.toString(), ok);
// rerun the failed test to get verbose output
if (!(ok | verbose)) exprMiniComp(expr, thisPtrs, lib, expRes,
cf_orig, retID_patchback, eval_methods,
true);
};
public static String toStr(OP o) {
if (o instanceof OPload) {
OPload op=(OPload)o;
if (op.resID==8) return "\""+op.what+"\"";
return op.what.toString()+(op.resID>9?'L':"ZBCSIJFDLV".charAt(op.resID));
};
if (o instanceof OPbinary) {
String[] opSymbols={
"+","-","*","/","%","&","|","^","==","!=","<",">=",
">","<=","<<",">>",">>>","&&","||","{}",".+."};
OPbinary op=(OPbinary)o;
return toStr(op.chi[0])+opSymbols[op.code]+toStr(op.chi[1]);
};
if (o instanceof OPunary) {
String[] opSymbols={"--","~","!","<RET>","(Z)","(B)",
"(C)","(S)","(I)","(J)",
"(F)","(D)","(L)","(POP)","->TSB","->STR"};
OPunary op=(OPunary)o;
return opSymbols[op.code]+toStr(op.chi[0]);
};
if (o instanceof OPcall) {
OPcall op=(OPcall)o;
if (op.m==null)
return "{"+op.nplv+"}";
else {
StringBuffer res=new StringBuffer(op.m.getName());
res.append('(');
for (int i=0;i<op.chi.length;i++) {
if (i>0) res.append(",");
res.append(toStr(op.chi[i]));
};
res.append(')');
return res.toString();
}
};
if (o instanceof OPcondtnl) {
OPcondtnl op=(OPcondtnl)o;
StringBuffer res=new StringBuffer();
if (op.chi[1]!=null)
res.append('(');
res.append(toStr(op.chi[0]));
if (op.chi[1]!=null) {
res.append('?');
res.append(toStr(op.chi[1]));
res.append(':');
res.append(toStr(op.chi[2]));
res.append(')');
}
return res.toString();
};
return "<<<<<OP TYPE NOT IDENTIFIED>>>>";
};
public static void dumpImage(ClassFile cf) throws Exception {
java.io.FileOutputStream fos=
new java.io.FileOutputStream("dump.class");
fos.write(cf.getImage());
fos.close();
};
}