package bsh.interpreter;
import bsh.*;
import bsh.ast.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author RLE <rafal.lewczuk@gmail.com>
*/
public class BshEvaluatingVisitor extends BshNodeVisitor<Object> {
private CallStack callstack;
private Interpreter interpreter;
public BshEvaluatingVisitor(CallStack callstack, Interpreter interpreter) {
this.callstack = callstack;
this.interpreter = interpreter;
}
public CallStack getCallstack() {
return callstack;
}
public Interpreter getInterpreter() {
return interpreter;
}
@Override
public Object visit(BSHAllocationExpression node) {
// type is either a class name or a primitive type
SimpleNode type = (SimpleNode)node.jjtGetChild(0);
// args is either constructor arguments or array dimensions
SimpleNode args = (SimpleNode)node.jjtGetChild(1);
if ( type instanceof BSHAmbiguousName)
{
BSHAmbiguousName name = (BSHAmbiguousName)type;
if (args instanceof BSHArguments)
return objectAllocation(node, name, (BSHArguments) args);
else
return objectArrayAllocation(node, name, (BSHArrayDimensions) args
);
}
else
return primitiveArrayAllocation(node, (BSHPrimitiveType) type,
(BSHArrayDimensions) args);
}
public Object objectAllocation(BSHAllocationExpression node,
BSHAmbiguousName nameNode, BSHArguments argumentsNode)
throws EvalError
{
NameSpace namespace = callstack.top();
Object[] args = getArguments(argumentsNode);
if ( args == null)
throw new EvalError( "Null args in new.", node, callstack );
// Look for scripted class object
Object obj = ambiguousNameToObject(nameNode, false/* force class*/ );
// Try regular class
obj = ambiguousNameToObject( nameNode, true/*force class*/ );
Class type = null;
if ( obj instanceof ClassIdentifier )
type = ((ClassIdentifier)obj).getTargetClass();
else
throw new EvalError(
"Unknown class: "+nameNode.text, node, callstack );
// Is an inner class style object allocation
boolean hasBody = node.jjtGetNumChildren() > 2;
if ( hasBody )
{
BSHBlock body = (BSHBlock)node.jjtGetChild(2);
if ( type.isInterface() )
return constructWithInterfaceBody(node,
type, args, body);
else
return constructWithClassBody( node,
type, args, body );
} else
return constructObject(node, type, args );
}
public Object constructObject(BSHAllocationExpression node, Class<?> type, Object[] args ) throws EvalError {
final boolean isGeneratedClass = GeneratedClass.class.isAssignableFrom(type);
if (isGeneratedClass) {
ClassGeneratorUtil.registerConstructorContext(callstack, interpreter);
}
Object obj;
try {
obj = Reflect.constructObject( type, args );
} catch ( ReflectError e) {
throw new EvalError(
"Constructor error: " + e.getMessage(), node, callstack );
} catch (InvocationTargetException e) {
// No need to wrap this debug
Interpreter.debug("The constructor threw an exception:\n\t" + e.getTargetException());
throw new TargetError("Object constructor", e.getTargetException(), node, callstack, true);
} finally {
if (isGeneratedClass) {
ClassGeneratorUtil.registerConstructorContext(null, null); // clean up, prevent memory leak
}
}
String className = type.getName();
// Is it an inner class?
if ( className.indexOf("$") == -1 )
return obj;
// Temporary hack to support inner classes
// If the obj is a non-static inner class then import the context...
// This is not a sufficient emulation of inner classes.
// Replace this later...
// work through to class 'this'
This ths = callstack.top().getThis( null );
NameSpace instanceNameSpace =
Name.getClassNameSpace( ths.getNameSpace() );
// Change the parent (which was the class static) to the class instance
// We really need to check if we're a static inner class here first...
// but for some reason Java won't show the static modifier on our
// fake inner classes... could generate a flag field.
if ( instanceNameSpace != null
&& className.startsWith( instanceNameSpace.getName() +"$")
)
{
ClassGenerator.getClassGenerator().setInstanceNameSpaceParent(
obj, className, instanceNameSpace );
}
return obj;
}
public Object constructWithClassBody( BSHAllocationExpression node,
Class type, Object[] args, BSHBlock block )
throws EvalError
{
String name = callstack.top().getName() + "$" + (++node.innerClassCount);
Modifiers modifiers = new Modifiers();
modifiers.addModifier( Modifiers.CLASS, "public" );
Class clas = ClassGenerator.getClassGenerator() .generateClass(
name, modifiers, null/*interfaces*/, type/*superClass*/,
block, false/*isInterface*/, callstack, interpreter );
try {
return Reflect.constructObject( clas, args );
} catch ( Exception e ) {
Throwable cause = e;
if ( e instanceof InvocationTargetException ) {
cause = ((InvocationTargetException) e).getTargetException();
}
throw new EvalError("Error constructing inner class instance: "+e, node, callstack, cause);
}
}
public Object constructWithInterfaceBody( BSHAllocationExpression node,
Class type, Object[] args, BSHBlock body )
throws EvalError
{
NameSpace namespace = callstack.top();
NameSpace local = new NameSpace(namespace, "AnonymousBlock");
callstack.push(local);
evalBlock(body, true);
callstack.pop();
// statical import fields from the interface so that code inside
// can refer to the fields directly (e.g. HEIGHT)
local.importStatic( type );
return local.getThis(interpreter).getInterface( type );
}
public Object objectArrayAllocation( BSHAllocationExpression node,
BSHAmbiguousName nameNode, BSHArrayDimensions dimensionsNode )
throws EvalError
{
NameSpace namespace = callstack.top();
Class type = ambiguousNameToClass( nameNode );
if ( type == null )
throw new EvalError( "Class " + nameNode.getName(namespace)
+ " not found.", node, callstack );
return arrayAllocation( node, dimensionsNode, type );
}
public Object primitiveArrayAllocation( BSHAllocationExpression node,
BSHPrimitiveType typeNode, BSHArrayDimensions dimensionsNode
)
throws EvalError
{
Class type = typeNode.getType();
return arrayAllocation(node, dimensionsNode, type );
}
public Object arrayAllocation( BSHAllocationExpression node,
BSHArrayDimensions dimensionsNode, Class type)
throws EvalError
{
/*
dimensionsNode can return either a fully intialized array or VOID.
when VOID the prescribed array dimensions (defined and undefined)
are contained in the node.
*/
Object result = evalArrayDimensions(dimensionsNode, type);
if ( result != Primitive.VOID )
return result;
else
return arrayNewInstance( node, type, dimensionsNode );
}
/**
Create an array of the dimensions specified in dimensionsNode.
dimensionsNode may contain a number of "undefined" as well as "defined"
dimensions.
<p>
Background: in Java arrays are implemented in arrays-of-arrays style
where, for example, a two dimensional array is a an array of arrays of
some base type. Each dimension-type has a Java class type associated
with it... so if foo = new int[5][5] then the type of foo is
int [][] and the type of foo[0] is int[], etc. Arrays may also be
specified with undefined trailing dimensions - meaning that the lower
order arrays are not allocated as objects. e.g.
if foo = new int [5][]; then foo[0] == null //true; and can later be
assigned with the appropriate type, e.g. foo[0] = new int[5];
(See Learning Java, O'Reilly & Associates more background).
<p>
To create an array with undefined trailing dimensions using the
reflection API we must use an array type to represent the lower order
(undefined) dimensions as the "base" type for the array creation...
Java will then create the correct type by adding the dimensions of the
base type to specified allocated dimensions yielding an array of
dimensionality base + specified with the base dimensons unallocated.
To create the "base" array type we simply create a prototype, zero
length in each dimension, array and use it to get its class
(Actually, I think there is a way we could do it with Class.forName()
but I don't trust this). The code is simpler than the explanation...
see below.
*/
public Object arrayNewInstance( BSHAllocationExpression node,
Class type, BSHArrayDimensions dimensionsNode )
throws EvalError
{
if ( dimensionsNode.numUndefinedDims > 0 )
{
Object proto = Array.newInstance(
type, new int [dimensionsNode.numUndefinedDims] ); // zeros
type = proto.getClass();
}
try {
return Array.newInstance(
type, dimensionsNode.definedDimensions);
} catch( NegativeArraySizeException e1 ) {
throw new TargetError( e1, node, callstack );
} catch( Exception e ) {
throw new EvalError("Can't construct primitive array: " +
e.getMessage(), node, callstack);
}
}
@Override
public Object visit(BSHAmbiguousName node) {
throw new InterpreterError(
"Don't know how to eval an ambiguous name!"
+" Use toObject() if you want an object." );
}
public Object ambiguousNameToObject( BSHAmbiguousName node )
throws EvalError
{
return ambiguousNameToObject( node, false );
}
public Object ambiguousNameToObject( BSHAmbiguousName node,
boolean forceClass )
throws EvalError
{
try {
return
node.getName( callstack.top() ).toObject(
this, forceClass );
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
public Class ambiguousNameToClass( BSHAmbiguousName node )
throws EvalError
{
try {
return node.getName( callstack.top() ).toClass();
} catch ( ClassNotFoundException e ) {
throw new EvalError( e.getMessage(), node, callstack, e );
} catch ( UtilEvalError e2 ) {
// ClassPathException is a type of UtilEvalError
throw e2.toEvalError( node, callstack );
}
}
public LHS ambiguousNameToLHS( BSHAmbiguousName node )
throws EvalError
{
try {
return node.getName( callstack.top() ).toLHS( this );
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
@Override
public Object visit(BSHArguments node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHArguments class.");
}
/*
Disallowing VOIDs here was an easy way to support the throwing of a
more descriptive error message on use of an undefined argument to a
method call (very common). If it ever turns out that we need to
support that for some reason we'll have to re-evaluate how we get
"meta-information" about the arguments in the various invoke() methods
that take Object []. We could either pass BSHArguments down to
overloaded forms of the methods or throw an exception subtype
including the argument position back up, where the error message would
be compounded.
*/
public Object[] getArguments( BSHArguments node )
throws EvalError
{
// evaluate each child
Object[] args = new Object[node.jjtGetNumChildren()];
for(int i = 0; i < args.length; i++)
{
args[i] = ((SimpleNode)node.jjtGetChild(i)).accept(this);
if ( args[i] == Primitive.VOID )
throw new EvalError( "Undefined argument: " +
((SimpleNode)node.jjtGetChild(i)).getText(), node, callstack );
}
return args;
}
/**
Evaluate the structure of the array in one of two ways:
a) an initializer exists, evaluate it and return
the fully constructed array object, also record the dimensions
of that array
b) evaluate and record the lengths in each dimension and
return void.
The structure of the array dims is maintained in dimensions.
*/
@Override
public Object visit(BSHArrayDimensions node) {
SimpleNode child = (SimpleNode)node.jjtGetChild(0);
/*
Child is array initializer. Evaluate it and fill in the
dimensions it returns. Initialized arrays are always fully defined
(no undefined dimensions to worry about).
The syntax uses the undefinedDimension count.
e.g. int [][] { 1, 2 };
*/
if (child instanceof BSHArrayInitializer)
{
if ( node.baseType == null )
throw new EvalError(
"Internal Array Eval err: unknown base type",
node, callstack );
Object initValue = evalArrayInitializer(((BSHArrayInitializer) child),
node.baseType, node.numUndefinedDims, callstack, interpreter);
Class arrayClass = initValue.getClass();
int actualDimensions = Reflect.getArrayDimensions(arrayClass);
node.definedDimensions = new int[ actualDimensions ];
// Compare with number of dimensions actually created with the
// number specified (syntax uses the undefined ones here)
if ( node.definedDimensions.length != node.numUndefinedDims )
throw new EvalError(
"Incompatible initializer. Allocation calls for a " +
node.numUndefinedDims+ " dimensional array, but initializer is a " +
actualDimensions + " dimensional array", node, callstack );
// fill in definedDimensions [] lengths
Object arraySlice = initValue;
for ( int i = 0; i < node.definedDimensions.length; i++ ) {
node.definedDimensions[i] = Array.getLength(arraySlice);
if ( node.definedDimensions[i] > 0 )
arraySlice = Array.get(arraySlice, 0);
}
return initValue;
}
else
// Evaluate the defined dimensions of the array
{
node.definedDimensions = new int[ node.numDefinedDims ];
for(int i = 0; i < node.numDefinedDims; i++)
{
try {
Object length = ((SimpleNode)node.jjtGetChild(i)).accept(this);
node.definedDimensions[i] = ((Primitive)length).intValue();
}
catch(Exception e)
{
throw new EvalError(
"Array index: " + i +
" does not evaluate to an integer", node, callstack );
}
}
}
return Primitive.VOID;
}
public Object evalArrayDimensions( BSHArrayDimensions node,
Class type )
throws EvalError
{
if ( Interpreter.DEBUG ) Interpreter.debug("array base type = "+type);
node.baseType = type;
return node.accept(this);
}
/**
Construct the array from the initializer syntax.
@param baseType the base class type of the array (no dimensionality)
@param dimensions the top number of dimensions of the array
e.g. 2 for a String [][];
*/
public Object evalArrayInitializer(BSHArrayInitializer nodeA, Class baseType, int dimensions,
CallStack callstack, Interpreter interpreter )
throws EvalError
{
int numInitializers = nodeA.jjtGetNumChildren();
// allocate the array to store the initializers
int [] dima = new int [dimensions]; // description of the array
// The other dimensions default to zero and are assigned when
// the values are set.
dima[0] = numInitializers;
Object initializers = Array.newInstance( baseType, dima );
// Evaluate the initializers
for (int i = 0; i < numInitializers; i++)
{
SimpleNode childNode = (SimpleNode)nodeA.jjtGetChild(i);
Object currentInitializer;
if ( childNode instanceof BSHArrayInitializer ) {
if ( dimensions < 2 )
throw new EvalError(
"Invalid Location for Intializer, position: "+i,
nodeA, callstack );
currentInitializer =
evalArrayInitializer(((BSHArrayInitializer)childNode),
baseType, dimensions-1, callstack, interpreter);
} else
currentInitializer = childNode.accept(this);
if ( currentInitializer == Primitive.VOID )
throw new EvalError(
"Void in array initializer, position"+i, nodeA, callstack );
// Determine if any conversion is necessary on the initializers.
//
// Quick test to see if conversions apply:
// If the dimensionality of the array is 1 then the elements of
// the initializer can be primitives or boxable types. If it is
// greater then the values must be array (object) types and there
// are currently no conversions that we do on those.
// If we have conversions on those in the future then we need to
// get the real base type here instead of the dimensionless one.
Object value = currentInitializer;
if ( dimensions == 1 )
{
// We do a bsh cast here. strictJava should be able to affect
// the cast there when we tighten control
try {
value = Types.castObject(
currentInitializer, baseType, Types.CAST );
} catch ( UtilEvalError e ) {
throw e.toEvalError(
"Error in array initializer", nodeA, callstack );
}
// unwrap any primitive, map voids to null, etc.
value = Primitive.unwrap( value );
}
// store the value in the array
try {
Array.set(initializers, i, value);
} catch( IllegalArgumentException e ) {
Interpreter.debug("illegal arg"+e);
throwTypeError( nodeA, baseType, currentInitializer, i, callstack );
} catch( ArrayStoreException e ) { // I think this can happen
Interpreter.debug("arraystore"+e);
throwTypeError(nodeA, baseType, currentInitializer, i, callstack );
}
}
return initializers;
}
private void throwTypeError( BSHArrayInitializer node,
Class baseType, Object initializer, int argNum, CallStack callstack )
throws EvalError
{
String rhsType;
if (initializer instanceof Primitive)
rhsType =
((Primitive)initializer).getType().getName();
else
rhsType = Reflect.normalizeClassName(
initializer.getClass());
throw new EvalError ( "Incompatible type: " + rhsType
+" in initializer of array type: "+ baseType
+" at position: "+argNum, node, callstack );
}
@Override
public Object visit(BSHArrayInitializer node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHArrayInitializer class.");
}
@Override
public Object visit(BSHAssignment node) {
BSHPrimaryExpression lhsNode =
(BSHPrimaryExpression)node.jjtGetChild(0);
if ( lhsNode == null )
throw new InterpreterError( "Error, null LHSnode" );
boolean strictJava = interpreter.getStrictJava();
LHS lhs = primaryExprToLHS(lhsNode);
if ( lhs == null )
throw new InterpreterError( "Error, null LHS" );
// For operator-assign operations save the lhs value before evaluating
// the rhs. This is correct Java behavior for postfix operations
// e.g. i=1; i+=i++; // should be 2 not 3
Object lhsValue = null;
if ( node.operator != ParserConstants.ASSIGN ) // assign doesn't need the pre-value
try {
lhsValue = lhs.getValue();
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
SimpleNode rhsNode = (SimpleNode)node.jjtGetChild(1);
Object rhs;
// implement "blocks" foo = { };
// if ( rhsNode instanceof BSHBlock )
// rsh =
// else
rhs = rhsNode.accept(this);
if ( rhs == Primitive.VOID )
throw new EvalError("Void assignment.", node, callstack );
try {
switch(node.operator)
{
case ParserConstants.ASSIGN:
return lhs.assign( rhs, strictJava );
case ParserConstants.PLUSASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.PLUS), strictJava );
case ParserConstants.MINUSASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.MINUS), strictJava );
case ParserConstants.STARASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.STAR), strictJava );
case ParserConstants.SLASHASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.SLASH), strictJava );
case ParserConstants.ANDASSIGN:
case ParserConstants.ANDASSIGNX:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.BIT_AND), strictJava );
case ParserConstants.ORASSIGN:
case ParserConstants.ORASSIGNX:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.BIT_OR), strictJava );
case ParserConstants.XORASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.XOR), strictJava );
case ParserConstants.MODASSIGN:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.MOD), strictJava );
case ParserConstants.LSHIFTASSIGN:
case ParserConstants.LSHIFTASSIGNX:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.LSHIFT), strictJava );
case ParserConstants.RSIGNEDSHIFTASSIGN:
case ParserConstants.RSIGNEDSHIFTASSIGNX:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.RSIGNEDSHIFT ), strictJava );
case ParserConstants.RUNSIGNEDSHIFTASSIGN:
case ParserConstants.RUNSIGNEDSHIFTASSIGNX:
return lhs.assign(
BshInterpreterUtil.operation(node, lhsValue, rhs, ParserConstants.RUNSIGNEDSHIFT),
strictJava );
default:
throw new InterpreterError(
"unimplemented operator in assignment BSH");
}
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
@Override
public Object visit(BSHBinaryExpression node) {
Object lhs = ((SimpleNode)node.jjtGetChild(0)).accept(this);
/*
Doing instanceof? Next node is a type.
*/
if (node.kind == ParserConstants.INSTANCEOF)
{
// null object ref is not instance of any type
if ( lhs == Primitive.NULL )
return new Primitive(false);
Class rhs = getType(((BSHType)node.jjtGetChild(1)));
/*
// primitive (number or void) cannot be tested for instanceof
if (lhs instanceof Primitive)
throw new EvalError("Cannot be instance of primitive type." );
*/
/*
Primitive (number or void) is not normally an instanceof
anything. But for internal use we'll test true for the
bsh.Primitive class.
i.e. (5 instanceof bsh.Primitive) will be true
*/
if ( lhs instanceof Primitive )
if ( rhs == bsh.Primitive.class )
return new Primitive(true);
else
return new Primitive(false);
// General case - performe the instanceof based on assignability
boolean ret = Types.isJavaBaseAssignable( rhs, lhs.getClass() );
return new Primitive(ret);
}
// The following two boolean checks were tacked on.
// This could probably be smoothed out.
/*
Look ahead and short circuit evaluation of the rhs if:
we're a boolean AND and the lhs is false.
*/
if ( node.kind == ParserConstants.BOOL_AND || node.kind == ParserConstants.BOOL_ANDX ) {
Object obj = lhs;
if ( node.isPrimitiveValue(lhs) )
obj = ((Primitive)lhs).getValue();
if ( obj instanceof Boolean &&
( ((Boolean)obj).booleanValue() == false ) )
return new Primitive(false);
}
/*
Look ahead and short circuit evaluation of the rhs if:
we're a boolean AND and the lhs is false.
*/
if ( node.kind == ParserConstants.BOOL_OR || node.kind == ParserConstants.BOOL_ORX ) {
Object obj = lhs;
if ( node.isPrimitiveValue(lhs) )
obj = ((Primitive)lhs).getValue();
if ( obj instanceof Boolean &&
( ((Boolean)obj).booleanValue() == true ) )
return new Primitive(true);
}
// end stuff that was tacked on for boolean short-circuiting.
/*
Are both the lhs and rhs either wrappers or primitive values?
do binary op
*/
boolean isLhsWrapper = node.isWrapper(lhs);
Object rhs = ((SimpleNode)node.jjtGetChild(1)).accept(this); //eval(callstack, interpreter);
boolean isRhsWrapper = node.isWrapper(rhs);
if (
( isLhsWrapper || node.isPrimitiveValue(lhs) )
&& ( isRhsWrapper || node.isPrimitiveValue(rhs) )
)
{
// Special case for EQ on two wrapper objects
if ( (isLhsWrapper && isRhsWrapper && node.kind == ParserConstants.EQ))
{
/*
Don't auto-unwrap wrappers (preserve identity semantics)
FALL THROUGH TO OBJECT OPERATIONS BELOW.
*/
} else
try {
return Primitive.binaryOperation(lhs, rhs, node.kind);
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
/*
Doing the following makes it hard to use untyped vars...
e.g. if ( arg == null ) ...what if arg is a primitive?
The answer is that we should test only if the var is typed...?
need to get that info here...
else
{
// Do we have a mixture of primitive values and non-primitives ?
// (primitiveValue = not null, not void)
int primCount = 0;
if ( isPrimitiveValue( lhs ) )
++primCount;
if ( isPrimitiveValue( rhs ) )
++primCount;
if ( primCount > 1 )
// both primitive types, should have been handled above
throw new InterpreterError("should not be here");
else
if ( primCount == 1 )
// mixture of one and the other
throw new EvalError("Operator: '" + tokenImage[kind]
+"' inappropriate for object / primitive combination.",
this, callstack );
// else fall through to handle both non-primitive types
// end check for primitive and non-primitive mix
}
*/
/*
Treat lhs and rhs as arbitrary objects and do the operation.
(including NULL and VOID represented by their Primitive types)
*/
//System.out.println("binary op arbitrary obj: {"+lhs+"}, {"+rhs+"}");
switch(node.kind)
{
case ParserConstants.EQ:
return new Primitive((lhs == rhs));
case ParserConstants.NE:
return new Primitive((lhs != rhs));
case ParserConstants.PLUS:
if(lhs instanceof String || rhs instanceof String)
return lhs.toString() + rhs.toString();
// FALL THROUGH TO DEFAULT CASE!!!
default:
if(lhs instanceof Primitive || rhs instanceof Primitive)
if ( lhs == Primitive.VOID || rhs == Primitive.VOID )
throw new EvalError(
"illegal use of undefined variable, class, or 'void' literal",
node, callstack );
else
if ( lhs == Primitive.NULL || rhs == Primitive.NULL )
throw new EvalError(
"illegal use of null value or 'null' literal", node, callstack);
throw new EvalError("Operator: '" + ParserConstants.tokenImage[node.kind] +
"' inappropriate for objects", node, callstack );
}
}
/**
@param overrideNamespace if set to true the block will be executed
in the current namespace (not a subordinate one).
<p>
If true *no* new BlockNamespace will be swapped onto the stack and
the eval will happen in the current
top namespace. This is used by BshMethod, TryStatement, etc.
which must intialize the block first and also for those that perform
multiple passes in the same block.
*/
public Object evalBlock( BSHBlock node,
boolean overrideNamespace )
throws EvalError
{
Object syncValue = null;
if ( node.isSynchronized )
{
// First node is the expression on which to sync
SimpleNode exp = ((SimpleNode)node.jjtGetChild(0));
syncValue = exp.accept(this);
}
Object ret;
if ( node.isSynchronized ) // Do the actual synchronization
synchronized( syncValue )
{
ret = evalBlock(node, overrideNamespace, null/*filter*/);
}
else
ret = evalBlock(node, overrideNamespace, null/*filter*/ );
return ret;
}
public Object evalBlock( BSHBlock block,
boolean overrideNamespace, BSHBlock.NodeFilter nodeFilter )
throws EvalError
{
Object ret = Primitive.VOID;
NameSpace enclosingNameSpace = null;
if ( !overrideNamespace )
{
enclosingNameSpace= callstack.top();
BlockNameSpace bodyNameSpace =
new BlockNameSpace( enclosingNameSpace );
callstack.swap( bodyNameSpace );
}
int startChild = block.isSynchronized ? 1 : 0;
int numChildren = block.jjtGetNumChildren();
try {
/*
Evaluate block in two passes:
First do class declarations then do everything else.
*/
for(int i=startChild; i<numChildren; i++)
{
SimpleNode node = ((SimpleNode)block.jjtGetChild(i));
if ( nodeFilter != null && !nodeFilter.isVisible( node ) )
continue;
if ( node instanceof BSHClassDeclaration )
node.accept(this);
}
for(int i=startChild; i<numChildren; i++)
{
SimpleNode node = ((SimpleNode)block.jjtGetChild(i));
if ( node instanceof BSHClassDeclaration )
continue;
// filter nodes
if ( nodeFilter != null && !nodeFilter.isVisible( node ) )
continue;
ret = node.accept(this);
// statement or embedded block evaluated a return statement
if ( ret instanceof ReturnControl )
break;
}
} finally {
// make sure we put the namespace back when we leave.
if ( !overrideNamespace )
callstack.swap( enclosingNameSpace );
}
return ret;
}
@Override
public Object visit(BSHBlock node) {
return evalBlock(node, false);
}
@Override
public Object visit(BSHCastExpression node) {
NameSpace namespace = callstack.top();
Class toType = getType(((BSHType)node.jjtGetChild(0)));
SimpleNode expression = (SimpleNode)node.jjtGetChild(1);
// evaluate the expression
Object fromValue = expression.accept(this);
Class fromType = fromValue.getClass();
// TODO: need to add isJavaCastable() test for strictJava
// (as opposed to isJavaAssignable())
try {
return Types.castObject( fromValue, toType, Types.CAST );
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
@Override
public Object visit(BSHClassDeclaration node) {
synchronized (node) {
if (node.generatedClass == null) {
node.generatedClass = generateClass(node);
}
return node.generatedClass;
}
}
public Class<?> generateClass(BSHClassDeclaration node) throws EvalError {
int child = 0;
// resolve superclass if any
Class superClass = null;
if ( node.extend ) {
BSHAmbiguousName superNode = (BSHAmbiguousName)node.jjtGetChild(child++);
superClass = ambiguousNameToClass( superNode );
}
// Get interfaces
Class [] interfaces = new Class[node.numInterfaces];
for( int i=0; i<node.numInterfaces; i++) {
BSHAmbiguousName node1 = (BSHAmbiguousName)node.jjtGetChild(child++);
interfaces[i] = ambiguousNameToClass(node1);
if ( !interfaces[i].isInterface() )
throw new EvalError(
"Type: "+node1.text+" is not an interface!",
node, callstack );
}
BSHBlock block;
// Get the class body BSHBlock
if ( child < node.jjtGetNumChildren() )
block = (BSHBlock) node.jjtGetChild(child);
else
block = new BSHBlock( ParserTreeConstants.JJTBLOCK );
return ClassGenerator.getClassGenerator().generateClass(
node.name, node.modifiers, interfaces, superClass, block, node.isInterface,
callstack, interpreter );
}
@Override
public Object visit(BSHEnhancedForStatement node) {
Class elementType = null;
SimpleNode expression, statement=null;
NameSpace enclosingNameSpace = callstack.top();
SimpleNode firstNode =((SimpleNode)node.jjtGetChild(0));
int nodeCount = node.jjtGetNumChildren();
if ( firstNode instanceof BSHType )
{
elementType=getType(((BSHType)firstNode));
expression=((SimpleNode)node.jjtGetChild(1));
if ( nodeCount>2 )
statement=((SimpleNode)node.jjtGetChild(2));
} else
{
expression=firstNode;
if ( nodeCount>1 )
statement=((SimpleNode)node.jjtGetChild(1));
}
BlockNameSpace eachNameSpace = new BlockNameSpace( enclosingNameSpace );
callstack.swap( eachNameSpace );
final Object iteratee = expression.accept(this);
if ( iteratee == Primitive.NULL )
throw new EvalError("The collection, array, map, iterator, or " +
"enumeration portion of a for statement cannot be null.",
node, callstack );
CollectionManager cm = CollectionManager.getCollectionManager();
if ( !cm.isBshIterable( iteratee ) )
throw new EvalError("Can't iterate over type: "
+iteratee.getClass(), node, callstack );
Iterator iterator = cm.getBshIterator( iteratee );
Object returnControl = Primitive.VOID;
while( iterator.hasNext() )
{
try {
Object value = iterator.next();
if ( value == null )
value = Primitive.NULL;
if ( elementType != null )
eachNameSpace.setTypedVariable(
node.varName/*name*/, elementType/*type*/,
value, new Modifiers()/*none*/ );
else
eachNameSpace.setVariable( node.varName, value, false );
} catch ( UtilEvalError e ) {
throw e.toEvalError(
"for loop iterator variable:"+ node.varName, node, callstack );
}
boolean breakout = false; // switch eats a multi-level break here?
if ( statement != null ) // not empty statement
{
Object ret = statement.accept(this); //eval( callstack, interpreter );
if (ret instanceof ReturnControl)
{
switch(((ReturnControl)ret).kind)
{
case ParserConstants.RETURN:
returnControl = ret;
breakout = true;
break;
case ParserConstants.CONTINUE:
break;
case ParserConstants.BREAK:
breakout = true;
break;
}
}
}
if (breakout)
break;
}
callstack.swap(enclosingNameSpace);
return returnControl;
}
@Override
public Object visit(BSHFormalComment node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHFormalComment class.");
}
@Override
public Object visit(BSHFormalParameter node) {
if ( node.jjtGetNumChildren() > 0 )
node.type = getType(((BSHType)node.jjtGetChild(0)));
else
node.type = node.UNTYPED;
return node.type;
}
public String getFormalParameterTypeDescriptor( BSHFormalParameter node,
String defaultPackage )
{
if ( node.jjtGetNumChildren() > 0 )
return getTypeDescriptor(((BSHType) node.jjtGetChild(0)), defaultPackage);
else
// this will probably not get used
return "Ljava/lang/Object;"; // Object type
}
@Override
public Object visit(BSHFormalParameters node) {
if ( node.paramTypes != null )
return node.paramTypes;
node.insureParsed();
Class [] paramTypes = new Class[node.numArgs];
for(int i=0; i<node.numArgs; i++)
{
BSHFormalParameter param = (BSHFormalParameter)node.jjtGetChild(i);
paramTypes[i] = (Class)param.accept(this); //eval( callstack, interpreter );
}
node.paramTypes = paramTypes;
return paramTypes;
}
public String [] getTypeDescriptors( BSHFormalParameters node, String defaultPackage )
{
if ( node.typeDescriptors != null )
return node.typeDescriptors;
node.insureParsed();
String [] typeDesc = new String[node.numArgs];
for(int i=0; i<node.numArgs; i++)
{
BSHFormalParameter param = (BSHFormalParameter)node.jjtGetChild(i);
typeDesc[i] = getFormalParameterTypeDescriptor(param, defaultPackage );
}
node.typeDescriptors = typeDesc;
return typeDesc;
}
@Override
public Object visit(BSHForStatement node) {
int i = 0;
if(node.hasForInit)
node.forInit = ((SimpleNode)node.jjtGetChild(i++));
if(node.hasExpression)
node.expression = ((SimpleNode)node.jjtGetChild(i++));
if(node.hasForUpdate)
node.forUpdate = ((SimpleNode)node.jjtGetChild(i++));
if(i < node.jjtGetNumChildren()) // should normally be
node.statement = ((SimpleNode)node.jjtGetChild(i));
NameSpace enclosingNameSpace= callstack.top();
BlockNameSpace forNameSpace = new BlockNameSpace( enclosingNameSpace );
/*
Note: some interesting things are going on here.
1) We swap instead of push... The primary mode of operation
acts like we are in the enclosing namespace... (super must be
preserved, etc.)
2) We do *not* call the body block eval with the namespace
override. Instead we allow it to create a second subordinate
BlockNameSpace child of the forNameSpace. Variable propogation
still works through the chain, but the block's child cleans the
state between iteration.
(which is correct Java behavior... see forscope4.bsh)
*/
// put forNameSpace it on the top of the stack
// Note: it's important that there is only one exit point from this
// method so that we can swap back the namespace.
callstack.swap( forNameSpace );
// Do the for init
if ( node.hasForInit )
node.forInit.accept(this);
Object returnControl = Primitive.VOID;
while(true)
{
if ( node.hasExpression )
{
boolean cond = BshInterpreterUtil.evaluateCondition(
node.expression, this);
if ( !cond )
break;
}
boolean breakout = false; // switch eats a multi-level break here?
if ( node.statement != null ) // not empty statement
{
// do *not* invoke special override for block... (see above)
Object ret = node.statement.accept(this);
if (ret instanceof ReturnControl)
{
switch(((ReturnControl)ret).kind)
{
case ParserConstants.RETURN:
returnControl = ret;
breakout = true;
break;
case ParserConstants.CONTINUE:
break;
case ParserConstants.BREAK:
breakout = true;
break;
}
}
}
if ( breakout )
break;
if ( node.hasForUpdate )
node.forUpdate.accept(this);
}
callstack.swap( enclosingNameSpace ); // put it back
return returnControl;
}
@Override
public Object visit(BSHIfStatement node) {
Object ret = null;
if( BshInterpreterUtil.evaluateCondition(
(SimpleNode) node.jjtGetChild(0), this) )
ret = ((SimpleNode)node.jjtGetChild(1)).accept(this);
else
if(node.jjtGetNumChildren() > 2)
ret = ((SimpleNode)node.jjtGetChild(2)).accept(this);
if(ret instanceof ReturnControl)
return ret;
else
return Primitive.VOID;
}
@Override
public Object visit(BSHImportDeclaration node) {
NameSpace namespace = callstack.top();
if ( node.superImport )
try {
namespace.doSuperImport();
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
else
{
if ( node.staticImport )
{
if ( node.importPackage )
{
Class clas = ambiguousNameToClass(((BSHAmbiguousName)node.jjtGetChild(0)));
namespace.importStatic( clas );
} else
throw new EvalError(
"static field imports not supported yet",
node, callstack );
} else
{
String name = ((BSHAmbiguousName)node.jjtGetChild(0)).text;
if ( node.importPackage )
namespace.importPackage(name);
else
namespace.importClass(name);
}
}
return Primitive.VOID;
}
@Override
public Object visit(BSHLiteral node) {
if (node.value == null)
throw new InterpreterError("Null in bsh literal: "+node.value);
return node.value;
}
@Override
public Object visit(BSHMethodDeclaration node) {
node.returnType = evalMethodReturnType(node);
evalNodes(node);
// Install an *instance* of this method in the namespace.
// See notes in BshMethod
// This is not good...
// need a way to update eval without re-installing...
// so that we can re-eval params, etc. when classloader changes
// look into this
NameSpace namespace = callstack.top();
BshMethod bshMethod = new BshMethod( node, namespace, node.modifiers );
try {
namespace.setMethod( bshMethod );
} catch ( UtilEvalError e ) {
throw e.toEvalError(node,callstack);
}
return Primitive.VOID;
}
public Class evalReturnType( BSHReturnType node ) throws EvalError
{
if ( node.isVoid )
return Void.TYPE;
else
return getType(getTypeNode(node));
}
public BSHType getTypeNode(BSHReturnType node) {
return (BSHType)node.jjtGetChild(0);
}
public String getTypeDescriptor( BSHReturnType node,
String defaultPackage )
{
if ( node.isVoid )
return "V";
else
return getTypeDescriptor(getTypeNode(node),
defaultPackage);
}
public String getReturnTypeDescriptor(BSHMethodDeclaration node,
String defaultPackage )
{
node.insureNodesParsed();
if ( node.returnTypeNode == null )
return null;
else
return getTypeDescriptor(node.returnTypeNode, defaultPackage );
}
public BSHReturnType getReturnTypeNode(BSHMethodDeclaration node) {
node.insureNodesParsed();
return node.returnTypeNode;
}
/**
Evaluate the return type node.
@return the type or null indicating loosely typed return
*/
public Class evalMethodReturnType( BSHMethodDeclaration node )
throws EvalError
{
node.insureNodesParsed();
if ( node.returnTypeNode != null )
return evalReturnType(node.returnTypeNode);
else
return null;
}
public void evalNodes(BSHMethodDeclaration node)
throws EvalError
{
node.insureNodesParsed();
// validate that the throws names are class names
for(int i=node.firstThrowsClause; i<node.numThrows+node.firstThrowsClause; i++)
ambiguousNameToClass(((BSHAmbiguousName)node.jjtGetChild(i)) );
node.paramsNode.accept(this);
// if strictJava mode, check for loose parameters and return type
if ( interpreter.getStrictJava() )
{
for(int i=0; i<node.paramsNode.paramTypes.length; i++)
if ( node.paramsNode.paramTypes[i] == null )
// Warning: Null callstack here. Don't think we need
// a stack trace to indicate how we sourced the method.
throw new EvalError(
"(Strict Java Mode) Undeclared argument type, parameter: " +
node.paramsNode.getParamNames()[i] + " in method: "
+ node.name, node, null );
if ( node.returnType == null )
// Warning: Null callstack here. Don't think we need
// a stack trace to indicate how we sourced the method.
throw new EvalError(
"(Strict Java Mode) Undeclared return type for method: "
+ node.name, node, null );
}
}
@Override
public Object visit(BSHMethodInvocation node) {
NameSpace namespace = callstack.top();
BSHAmbiguousName nameNode = node.getNameNode();
// Do not evaluate methods this() or super() in class instance space
// (i.e. inside a constructor)
if ( namespace.getParent() != null && namespace.getParent().isClass
&& ( nameNode.text.equals("super") || nameNode.text.equals("this") )
)
return Primitive.VOID;
Name name = nameNode.getName(namespace);
Object[] args = getArguments(node.getArgsNode());
// This try/catch block is replicated is BSHPrimarySuffix... need to
// factor out common functionality...
// Move to Reflect?
try {
return name.invokeMethod( this, args, node);
} catch ( ReflectError e ) {
throw new EvalError(
"Error in method invocation: " + e.getMessage(),
node, callstack, e );
} catch ( InvocationTargetException e )
{
String msg = "Method Invocation "+name;
Throwable te = e.getTargetException();
/*
Try to squeltch the native code stack trace if the exception
was caused by a reflective call back into the bsh interpreter
(e.g. eval() or source()
*/
boolean isNative = true;
if ( te instanceof EvalError )
if ( te instanceof TargetError )
isNative = ((TargetError)te).inNativeCode();
else
isNative = false;
throw new TargetError( msg, te, node, callstack, isNative );
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
@Override
public Object visit(BSHPackageDeclaration node) {
BSHAmbiguousName name = (BSHAmbiguousName)node.jjtGetChild(0);
NameSpace namespace = callstack.top();
namespace.setPackage( name.text );
// import the package we're in by default...
namespace.importPackage( name.text );
return Primitive.VOID;
}
/**
Evaluate to a value object.
*/
private LHS primaryExprToLHS(BSHPrimaryExpression node)
throws EvalError
{
Object obj = evalPrimaryExpr(node, true);
if ( ! (obj instanceof LHS) )
throw new EvalError("Can't assign to:", node, callstack );
else
return (LHS)obj;
}
/*
Our children are a prefix expression and any number of suffixes.
<p>
We don't eval() any nodes until the suffixes have had an
opportunity to work through them. This lets the suffixes decide
how to interpret an ambiguous name (e.g. for the .class operation).
*/
private Object evalPrimaryExpr( BSHPrimaryExpression node, boolean toLHS )
throws EvalError
{
//CallStack callstack = visitor.getCallstack();
//Interpreter interpreter = visitor.getInterpreter();
Object obj = node.jjtGetChild(0);
int numChildren = node.jjtGetNumChildren();
for(int i=1; i<numChildren; i++)
obj = doSuffix(((BSHPrimarySuffix)node.jjtGetChild(i)), obj, toLHS);
/*
If the result is a Node eval() it to an object or LHS
(as determined by toLHS)
*/
if ( obj instanceof SimpleNode )
if ( obj instanceof BSHAmbiguousName)
if ( toLHS )
obj = ambiguousNameToLHS(((BSHAmbiguousName)obj));
else
obj = ambiguousNameToObject(((BSHAmbiguousName)obj));
else
// Some arbitrary kind of node
if ( toLHS )
// is this right?
throw new EvalError("Can't assign to prefix.",
node, callstack );
else
obj = ((SimpleNode)obj).accept(this);
// return LHS or value object as determined by toLHS
if ( obj instanceof LHS )
if ( toLHS )
return obj;
else
try {
return ((LHS)obj).getValue();
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
else
return obj;
}
@Override
public Object visit(BSHPrimaryExpression node) {
return evalPrimaryExpr(node, false);
}
private Object doSuffix( BSHPrimarySuffix node,
Object obj, boolean toLHS)
throws EvalError
{
// Handle ".class" suffix operation
// Prefix must be a BSHType
if ( node.operation == BSHPrimarySuffix.CLASS )
if ( obj instanceof BSHType) {
if ( toLHS )
throw new EvalError("Can't assign .class",
node, callstack );
NameSpace namespace = callstack.top();
return getType(((BSHType)obj));
} else
throw new EvalError(
"Attempt to use .class suffix on non class.",
node, callstack );
/*
Evaluate our prefix if it needs evaluating first.
If this is the first evaluation our prefix mayb be a Node
(directly from the PrimaryPrefix) - eval() it to an object.
If it's an LHS, resolve to a value.
Note: The ambiguous name construct is now necessary where the node
may be an ambiguous name. If this becomes common we might want to
make a static method nodeToObject() or something. The point is
that we can't just eval() - we need to direct the evaluation to
the context sensitive type of result; namely object, class, etc.
*/
if ( obj instanceof SimpleNode )
if ( obj instanceof BSHAmbiguousName)
obj = ambiguousNameToObject(((BSHAmbiguousName)obj));
else
obj = ((SimpleNode)obj).accept(this);
else
if ( obj instanceof LHS )
try {
obj = ((LHS)obj).getValue();
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
try
{
switch(node.operation)
{
case BSHPrimarySuffix.INDEX:
return doIndex(node, obj, toLHS );
case BSHPrimarySuffix.NAME:
return doName(node, obj, toLHS );
case BSHPrimarySuffix.PROPERTY:
return doProperty( node, toLHS, obj);
default:
throw new InterpreterError( "Unknown suffix type" );
}
}
catch(ReflectError e)
{
throw new EvalError("reflection error: " + e, node, callstack, e );
}
catch(InvocationTargetException e)
{
throw new TargetError( "target exception", e.getTargetException(),
node, callstack, true);
}
}
/*
Field access, .length on array, or a method invocation
Must handle toLHS case for each.
*/
private Object doName( BSHPrimarySuffix node, Object obj, boolean toLHS)
throws EvalError, ReflectError, InvocationTargetException
{
try {
// .length on array
if ( node.field.equals("length") && obj.getClass().isArray() )
if ( toLHS )
throw new EvalError(
"Can't assign array length", node, callstack );
else
return new Primitive(Array.getLength(obj));
// field access
if ( node.jjtGetNumChildren() == 0 )
if ( toLHS )
return Reflect.getLHSObjectField(obj, node.field);
else
return Reflect.getObjectFieldValue( obj, node.field );
// Method invocation
// (LHS or non LHS evaluation can both encounter method calls)
Object[] oa = getArguments(((BSHArguments)node.jjtGetChild(0)));
// TODO:
// Note: this try/catch block is copied from BSHMethodInvocation
// we need to factor out this common functionality and make sure
// we handle all cases ... (e.g. property style access, etc.)
// maybe move this to Reflect ?
try {
return Reflect.invokeObjectMethod(
obj, node.field, oa, this, node );
} catch ( ReflectError e ) {
throw new EvalError(
"Error in method invocation: " + e.getMessage(),
node, callstack, e );
} catch ( InvocationTargetException e )
{
String msg = "Method Invocation "+node.field;
Throwable te = e.getTargetException();
/*
Try to squeltch the native code stack trace if the exception
was caused by a reflective call back into the bsh interpreter
(e.g. eval() or source()
*/
boolean isNative = true;
if ( te instanceof EvalError )
if ( te instanceof TargetError )
isNative = ((TargetError)te).inNativeCode();
else
isNative = false;
throw new TargetError( msg, te, node, callstack, isNative );
}
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
/**
array index.
Must handle toLHS case.
*/
private Object doIndex( BSHPrimarySuffix node,
Object obj, boolean toLHS )
throws EvalError, ReflectError
{
int index = BshInterpreterUtil.getIndexAux( obj, this, node );
if ( toLHS )
return new LHS(obj, index);
else
try {
return Reflect.getIndex(obj, index);
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
/**
Property access.
Must handle toLHS case.
*/
private Object doProperty( BSHPrimarySuffix node, boolean toLHS, Object obj )
throws EvalError
{
if(obj == Primitive.VOID)
throw new EvalError(
"Attempt to access property on undefined variable or class name",
node, callstack );
if ( obj instanceof Primitive )
throw new EvalError("Attempt to access property on a primitive",
node, callstack );
Object value = ((SimpleNode)node.jjtGetChild(0)).accept(this);
if ( !( value instanceof String ) )
throw new EvalError(
"Property expression must be a String or identifier.",
node, callstack );
if ( toLHS )
return new LHS(obj, (String)value);
// Property style access to Hashtable or Map
CollectionManager cm = CollectionManager.getCollectionManager();
if ( cm.isMap( obj ) )
{
Object val = cm.getFromMap( obj, value/*key*/ );
return ( val == null ? val = Primitive.NULL : val );
}
try {
return Reflect.getObjectProperty( obj, (String)value );
}
catch ( UtilEvalError e)
{
throw e.toEvalError( "Property: "+value, node, callstack );
}
catch (ReflectError e)
{
throw new EvalError("No such property: " + value, node, callstack );
}
}
@Override
public Object visit(BSHPrimarySuffix node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHPrimarySuffix class.", node);
}
@Override
public Object visit(BSHPrimitiveType node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHPrimitiveType class.", node);
}
@Override
public Object visit(BSHReturnStatement node) {
Object value;
if(node.jjtGetNumChildren() > 0)
value = ((SimpleNode)node.jjtGetChild(0)).accept(this);
else
value = Primitive.VOID;
return new ReturnControl( node.kind, value, node );
}
@Override
public Object visit(BSHReturnType node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHPrimarySuffix class.");
}
@Override
public Object visit(BSHStatementExpressionList node) {
int n = node.jjtGetNumChildren();
for(int i=0; i<n; i++)
{
SimpleNode nn = ((SimpleNode)node.jjtGetChild(i));
nn.accept(this);
}
return Primitive.VOID;
}
@Override
public Object visit(BSHSwitchLabel node) {
if ( node.isDefault )
return null; // should probably error
SimpleNode label = ((SimpleNode)node.jjtGetChild(0));
return label.accept(this);
}
@Override
public Object visit(BSHSwitchStatement node) {
int numchild = node.jjtGetNumChildren();
int child = 0;
SimpleNode switchExp = ((SimpleNode)node.jjtGetChild(child++));
Object switchVal = switchExp.accept(this);
/*
Note: this could be made clearer by adding an inner class for the
cases and an object context for the child traversal.
*/
// first label
BSHSwitchLabel label;
Object obj;
ReturnControl returnControl=null;
// get the first label
if ( child >= numchild )
throw new EvalError("Empty switch statement.", node, callstack );
label = ((BSHSwitchLabel)node.jjtGetChild(child++));
// while more labels or blocks and haven't hit return control
while ( child < numchild && returnControl == null )
{
// if label is default or equals switchVal
if ( label.isDefault
|| primitiveEquals(node, switchVal, label.accept(this), switchExp) ) {
// execute nodes, skipping labels, until a break or return
while ( child < numchild )
{
obj = node.jjtGetChild(child++);
if ( obj instanceof BSHSwitchLabel )
continue;
// eval it
Object value =
((SimpleNode)obj).accept(this);
// should check to disallow continue here?
if ( value instanceof ReturnControl ) {
returnControl = (ReturnControl)value;
break;
}
}
} else
{
// skip nodes until next label
while ( child < numchild )
{
obj = node.jjtGetChild(child++);
if ( obj instanceof BSHSwitchLabel ) {
label = (BSHSwitchLabel)obj;
break;
}
}
}
}
if ( returnControl != null && returnControl.kind == ParserConstants.RETURN )
return returnControl;
else
return Primitive.VOID;
}
/**
Helper method for testing equals on two primitive or boxable objects.
yuck: factor this out into Primitive.java
*/
public boolean primitiveEquals( BSHSwitchStatement node,
Object switchVal, Object targetVal,
SimpleNode switchExp )
throws EvalError
{
if ( switchVal instanceof Primitive || targetVal instanceof Primitive )
try {
// binaryOperation can return Primitive or wrapper type
Object result = Primitive.binaryOperation(
switchVal, targetVal, ParserConstants.EQ );
result = Primitive.unwrap( result );
return result.equals( Boolean.TRUE );
} catch ( UtilEvalError e ) {
throw e.toEvalError(
"Switch value: "+switchExp.getText()+": ",
node, callstack );
}
else
return switchVal.equals( targetVal );
}
@Override
public Object visit(BSHTernaryExpression node) {
SimpleNode
cond = (SimpleNode)node.jjtGetChild(0),
evalTrue = (SimpleNode)node.jjtGetChild(1),
evalFalse = (SimpleNode)node.jjtGetChild(2);
if ( BshInterpreterUtil.evaluateCondition(cond, this) )
return evalTrue.accept(this);
else
return evalFalse.accept(this);
}
@Override
public Object visit(BSHThrowStatement node) {
Object obj = ((SimpleNode)node.jjtGetChild(0)).accept(this);
// need to loosen this to any throwable... do we need to handle
// that in interpreter somewhere? check first...
if(!(obj instanceof Exception))
throw new EvalError("Expression in 'throw' must be Exception type",
node, callstack );
// wrap the exception in a TargetException to propogate it up
throw new TargetError( (Exception)obj, node, callstack );
}
@Override
public Object visit(BSHTryStatement node) {
BSHBlock tryBlock = ((BSHBlock)node.jjtGetChild(0));
List<BSHFormalParameter> catchParams = new ArrayList<BSHFormalParameter>();
List<BSHBlock> catchBlocks = new ArrayList<BSHBlock>();
int nchild = node.jjtGetNumChildren();
Node nodeObj = null;
int i=1;
while((i < nchild) && ((nodeObj = node.jjtGetChild(i++)) instanceof BSHFormalParameter))
{
catchParams.add((BSHFormalParameter)nodeObj);
catchBlocks.add((BSHBlock)node.jjtGetChild(i++));
nodeObj = null;
}
// finaly block
BSHBlock finallyBlock = null;
if(nodeObj != null)
finallyBlock = (BSHBlock)nodeObj;
// Why both of these?
TargetError target = null;
Throwable thrown = null;
Object ret = null;
/*
Evaluate the contents of the try { } block and catch any resulting
TargetErrors generated by the script.
We save the callstack depth and if an exception is thrown we pop
back to that depth before contiuing. The exception short circuited
any intervening method context pops.
Note: we the stack info... what do we do with it? append
to exception message?
*/
int callstackDepth = callstack.depth();
try {
ret = tryBlock.accept(this);
}
catch( TargetError e ) {
target = e;
String stackInfo = "Bsh Stack: ";
while ( callstack.depth() > callstackDepth )
stackInfo += "\t" + callstack.pop() +"\n";
}
// unwrap the target error
if ( target != null )
thrown = target.getTarget();
// If we have an exception, find a catch
if (thrown != null)
{
int n = catchParams.size();
for(i=0; i<n; i++)
{
// Get catch block
BSHFormalParameter fp = catchParams.get(i);
// Should cache this subject to classloader change message
// Evaluation of the formal parameter simply resolves its
// type via the specified namespace.. it doesn't modify the
// namespace.
fp.accept(this);
if ( fp.type == null && interpreter.getStrictJava() )
throw new EvalError(
"(Strict Java) Untyped catch block", node, callstack );
// If the param is typed check assignability
if ( fp.type != null )
try {
thrown = (Throwable)Types.castObject(
thrown/*rsh*/, fp.type/*lhsType*/, Types.ASSIGNMENT );
} catch( UtilEvalError e ) {
/*
Catch the mismatch and continue to try the next
Note: this is innefficient, should have an
isAssignableFrom() that doesn't throw
// TODO: we do now have a way to test assignment
// in castObject(), use it?
*/
continue;
}
// Found match, execute catch block
BSHBlock cb = catchBlocks.get(i);
// Prepare to execute the block.
// We must create a new BlockNameSpace to hold the catch
// parameter and swap it on the stack after initializing it.
NameSpace enclosingNameSpace = callstack.top();
BlockNameSpace cbNameSpace =
new BlockNameSpace( enclosingNameSpace );
try {
if ( fp.type == BSHFormalParameter.UNTYPED )
// set an untyped variable directly in the block
cbNameSpace.setBlockVariable( fp.name, thrown );
else
{
// set a typed variable (directly in the block)
Modifiers modifiers = new Modifiers();
cbNameSpace.setTypedVariable(
fp.name, fp.type, thrown, new Modifiers()/*none*/ );
}
} catch ( UtilEvalError e ) {
throw new InterpreterError(
"Unable to set var in catch block namespace." );
}
// put cbNameSpace on the top of the stack
callstack.swap( cbNameSpace );
try {
ret = cb.accept(this);
} finally {
// put it back
callstack.swap( enclosingNameSpace );
}
target = null; // handled target
break;
}
}
// evaluate finally block
if( finallyBlock != null ) {
Object result = finallyBlock.accept(this);
if( result instanceof ReturnControl )
return result;
}
// exception fell through, throw it upward...
if(target != null)
throw target;
if(ret instanceof ReturnControl)
return ret;
else
return Primitive.VOID;
}
@Override
public Object visit(BSHType node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHType class.", node);
}
/**
Returns a class descriptor for this type.
If the type is an ambiguous name (object type) evaluation is
attempted through the namespace in order to resolve imports.
If it is not found and the name is non-compound we assume the default
package for the name.
*/
public String getTypeDescriptor( BSHType node, String defaultPackage )
{
// return cached type if available
if ( node.descriptor != null )
return node.descriptor;
String descriptor;
// first typeNode will either be PrimitiveType or AmbiguousName
SimpleNode typeNode = node.getTypeNode();
if ( typeNode instanceof BSHPrimitiveType)
descriptor = BshInterpreterUtil.getTypeDescriptor( ((BSHPrimitiveType)typeNode).type );
else
{
String clasName = ((BSHAmbiguousName)typeNode).text;
BshClassManager bcm = interpreter.getClassManager();
// Note: incorrect here - we are using the hack in bsh class
// manager that allows lookup by base name. We need to eliminate
// this limitation by working through imports. See notes in class
// manager.
String definingClass = bcm.getClassBeingDefined( clasName );
Class clas = null;
if ( definingClass == null )
{
try {
clas = ambiguousNameToClass(((BSHAmbiguousName)typeNode) );
} catch ( EvalError e ) {
//throw new InterpreterError("unable to resolve type: "+e);
// ignore and try default package
//System.out.println("BSHType: "+typeNode+" class not found");
}
} else
clasName = definingClass;
if ( clas != null )
{
//System.out.println("found clas: "+clas);
descriptor = BshInterpreterUtil.getTypeDescriptor( clas );
}else
{
if ( defaultPackage == null || Name.isCompound( clasName ) )
descriptor = "L" + clasName.replace('.','/') + ";";
else
descriptor =
"L"+defaultPackage.replace('.','/')+"/"+clasName + ";";
}
}
for(int i=0; i<node.arrayDims; i++)
descriptor = "["+descriptor;
node.descriptor = descriptor;
//System.out.println("BSHType: returning descriptor: "+descriptor);
return descriptor;
}
public Class getType(BSHType tnode)
throws EvalError
{
// return cached type if available
if ( tnode.type != null )
return tnode.type;
// first node will either be PrimitiveType or AmbiguousName
SimpleNode node = tnode.getTypeNode();
if ( node instanceof BSHPrimitiveType )
tnode.baseType = ((BSHPrimitiveType)node).getType();
else
tnode.baseType = ambiguousNameToClass(((BSHAmbiguousName)node));
if ( tnode.arrayDims > 0 ) {
try {
// Get the type by constructing a prototype array with
// arbitrary (zero) length in each dimension.
int[] dims = new int[tnode.arrayDims]; // int array default zeros
Object obj = Array.newInstance(tnode.baseType, dims);
tnode.type = obj.getClass();
} catch(Exception e) {
throw new EvalError("Couldn't construct array type",
tnode, callstack );
}
} else
tnode.type = tnode.baseType;
// hack... sticking to first interpreter that resolves this
// see comments on type instance variable
interpreter.getClassManager().addListener(tnode);
return tnode.type;
}
@Override
public Object visit(BSHTypedVariableDeclaration node) {
try {
NameSpace namespace = callstack.top();
BSHType typeNode = node.getTypeNode();
Class type = getType(typeNode);
BSHVariableDeclarator [] bvda = node.getDeclarators();
for (int i = 0; i < bvda.length; i++)
{
BSHVariableDeclarator dec = bvda[i];
// Type node is passed down the chain for array initializers
// which need it under some circumstances
Object value = evalVariableDeclarator(dec, typeNode);
try {
namespace.setTypedVariable(
dec.name, type, value, node.modifiers );
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
} catch ( EvalError e ) {
e.reThrow( "Typed variable declaration" );
}
return Primitive.VOID;
}
public Class evalType( BSHTypedVariableDeclaration node )
throws EvalError
{
BSHType typeNode = node.getTypeNode();
return getType( typeNode );
}
@Override
public Object visit(BSHUnaryExpression node) {
SimpleNode simpleNode = (SimpleNode)node.jjtGetChild(0);
// If this is a unary increment of decrement (either pre or postfix)
// then we need an LHS to which to assign the result. Otherwise
// just do the unary operation for the value.
try {
if ( node.kind == ParserConstants.INCR || node.kind == ParserConstants.DECR ) {
LHS lhs = primaryExprToLHS((BSHPrimaryExpression) simpleNode);
return node.lhsUnaryOperation(lhs, interpreter.getStrictJava());
} else
return
node.unaryOperation(simpleNode.accept(this), node.kind);
} catch ( UtilEvalError e ) {
throw e.toEvalError( node, callstack );
}
}
/**
Evaluate the optional initializer value.
(The name was set at parse time.)
A variable declarator can be evaluated with or without preceding
type information. Currently the type info is only used by array
initializers in the case where there is no explicitly declared type.
@param typeNode is the BSHType node. Its info is passed through to any
variable intializer children for the case where the array initializer
does not declare the type explicitly. e.g.
int [] a = { 1, 2 };
typeNode may be null to indicate no type information available.
*/
private Object evalVariableDeclarator(BSHVariableDeclarator node, BSHType typeNode)
throws EvalError
{
// null value means no value
Object value = null;
if ( node.jjtGetNumChildren() > 0 )
{
SimpleNode initializer = (SimpleNode)node.jjtGetChild(0);
/*
If we have type info and the child is an array initializer
pass it along... Else use the default eval style.
(This allows array initializer to handle the problem...
allowing for future enhancements in loosening types there).
*/
if ( (typeNode != null)
&& initializer instanceof BSHArrayInitializer
)
value = evalArrayInitializer(((BSHArrayInitializer) initializer),
typeNode.getBaseType(), typeNode.getArrayDims(),
callstack, interpreter);
else
value = initializer.accept(this);
}
if ( value == Primitive.VOID )
throw new EvalError("Void initializer.", node, callstack );
return value;
}
@Override
public Object visit(BSHVariableDeclarator node) {
throw new InterpreterError(
"Unimplemented or inappropriate for BSHVariableDeclarator class.");
}
@Override
public Object visit(BSHWhileStatement node) {
int numChild = node.jjtGetNumChildren();
// Order of body and condition is swapped for do / while
final SimpleNode condExp;
final SimpleNode body;
if ( node.isDoStatement ) {
condExp = (SimpleNode) node.jjtGetChild(1);
body = (SimpleNode) node.jjtGetChild(0);
} else {
condExp = (SimpleNode) node.jjtGetChild(0);
if ( numChild > 1 ) {
body = (SimpleNode) node.jjtGetChild(1);
} else {
body = null;
}
}
boolean doOnceFlag = node.isDoStatement;
while (doOnceFlag || BshInterpreterUtil.evaluateCondition(condExp, this)) {
doOnceFlag = false;
// no body?
if ( body == null ) {
continue;
}
Object ret = body.accept(this);
if (ret instanceof ReturnControl) {
switch(( (ReturnControl)ret).kind ) {
case ParserConstants.RETURN:
return ret;
case ParserConstants.CONTINUE:
break;
case ParserConstants.BREAK:
return Primitive.VOID;
}
}
}
return Primitive.VOID;
}
}