/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.core.interpret;
import org.eclipse.wst.jsdt.core.UnimplementedException;
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode;
import org.eclipse.wst.jsdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Assignment;
import org.eclipse.wst.jsdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Block;
import org.eclipse.wst.jsdt.internal.compiler.ast.BreakStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.ContinueStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.EqualExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Expression;
import org.eclipse.wst.jsdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.FunctionExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.IfStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.IntLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.MessageSend;
import org.eclipse.wst.jsdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.NullLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.ObjectLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.ObjectLiteralField;
import org.eclipse.wst.jsdt.internal.compiler.ast.OperatorIds;
import org.eclipse.wst.jsdt.internal.compiler.ast.PostfixExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.PrefixExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.ProgramElement;
import org.eclipse.wst.jsdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.StringLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.ThisReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.UndefinedLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.WhileStatement;
import org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ClassScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.Scope;
import org.eclipse.wst.jsdt.internal.core.interpret.builtin.BuiltInString;
public class InterpreterEngine extends ASTVisitor implements Contants{
protected InterpreterContext context;
InterpreterResult result = new InterpreterResult();
static final int STOP_RETURN =1;
static final int STOP_BREAK =2;
static final int STOP_CONTINUE =3;
static final int STOP_THROW =4;
class ExprStackItem extends Value{
ValueReference reference;
char [] referenceName;
int value;
Object objValue;
ExprStackItem()
{super(0);}
public int numberValue()
{
switch (type)
{
case Value.BOOLEAN:
case Value.NUMBER:
return value;
case Value.STRING:
return Integer.valueOf((String)objValue).intValue();
default:
throw new UnimplementedException();
}
}
public String stringValue()
{
switch (type)
{
case Value.BOOLEAN:
return value!=0 ?"true":"false"; //$NON-NLS-1$ //$NON-NLS-2$
case Value.NUMBER:
return String.valueOf(value);
case Value.STRING:
return (String)objValue;
default:
throw new UnimplementedException();
}
}
public boolean booleanValue()
{
switch (type)
{
case Value.BOOLEAN:
case Value.NUMBER:
return value!=0 ;
case Value.STRING:
return ((String)objValue).length()!=0;
default:
throw new UnimplementedException();
}
}
public ObjectValue getObjectValue()
{
switch (type)
{
case OBJECT:
case FUNCTION:
return (ObjectValue)objValue;
case UNDEFINED:
throw new InterpretException("null reference"); //$NON-NLS-1$
case BOOLEAN:
{
ObjectValue obj= new ObjectValue();
obj.setValue(VALUE_ARR,this);
return obj;
}
case NUMBER:
{
ObjectValue obj= new ObjectValue();
obj.setValue(VALUE_ARR,this);
return obj;
}
case STRING:
{
ObjectValue obj= new ObjectValue(BuiltInString.prototype);
obj.setValue(VALUE_ARR,new StringValue(this.stringValue()));
return obj;
}
}
throw new UnimplementedException();
}
Value getValue()
{
switch (type)
{
case NULL:
case UNDEFINED:
return (Value)objValue;
case BOOLEAN:
return new BooleanValue(value!=0);
case NUMBER:
return new NumberValue(value);
case STRING:
return new StringValue((String)objValue);
case OBJECT:
return (ObjectValue)objValue;
case FUNCTION:
return (FunctionValue)objValue;
}
throw new UnimplementedException();
}
public Object valueObject() {
return objValue;
}
}
ExprStackItem []stack=new ExprStackItem[30];
int stackPtr=-1;
public InterpreterEngine(InterpreterContext context) {
this.context=context;
for (int i=0;i<stack.length;i++)
stack[i]=new ExprStackItem();
}
public InterpreterResult interpret(CompilationUnitDeclaration ast)
{
if (ast.ignoreFurtherInvestigation)
throw new InterpretException("compile errors"); //$NON-NLS-1$
execBlock(ast.statements);
if (stackPtr>=0)
result.result=stack[stackPtr--];
else
result.result=Value.UndefinedObjectValue;
return result;
}
public void endVisit(BinaryExpression binaryExpression, BlockScope scope) {
ExprStackItem value2= stack[stackPtr--];
ExprStackItem value1= stack[stackPtr--];
int resultInt=0;
Object resultObj=null;
int type=Value.BOOLEAN; // most common
int operator=(binaryExpression.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
switch (operator)
{
case OperatorIds.PLUS:
if (value1.type==Value.STRING || value2.type == Value.STRING)
{
resultObj = value1.stringValue()+value2.stringValue();
type=Value.STRING;
}
else
{
resultInt = value1.numberValue()+value2.numberValue();
type=Value.NUMBER;
}
break;
case OperatorIds.MINUS:
resultInt = value1.numberValue()-value2.numberValue();
type=Value.NUMBER;
break;
case OperatorIds.DIVIDE:
resultInt = value1.numberValue() / value2.numberValue();
type=Value.NUMBER;
break;
case OperatorIds.MULTIPLY:
resultInt = value1.numberValue() * value2.numberValue();
type=Value.NUMBER;
break;
case OperatorIds.REMAINDER:
resultInt = value1.numberValue() % value2.numberValue();
type=Value.NUMBER;
break;
case OperatorIds.GREATER:
resultInt = (value1.numberValue() > value2.numberValue()) ?1:0;
break;
case OperatorIds.GREATER_EQUAL:
resultInt = (value1.numberValue() >= value2.numberValue())?1:0;
break;
case OperatorIds.LESS:
resultInt = (value1.numberValue() < value2.numberValue())?1:0;
break;
case OperatorIds.LESS_EQUAL:
resultInt = (value1.numberValue() <= value2.numberValue())?1:0;
break;
case OperatorIds.AND_AND:
resultInt = (value1.booleanValue() && value2.booleanValue())?1:0;
break;
case OperatorIds.AND:
resultInt = value1.numberValue() & value2.numberValue();
break;
case OperatorIds.OR:
resultInt = value1.numberValue() | value2.numberValue();
break;
case OperatorIds.OR_OR:
resultInt = (value1.booleanValue() || value2.booleanValue())?1:0;
break;
default:
throw new UnimplementedException(""); //$NON-NLS-1$
}
pushValue(type,resultInt,resultObj);
}
public void endVisit(EqualExpression equalExpression, BlockScope scope) {
ExprStackItem value2= stack[stackPtr--];
ExprStackItem value1= stack[stackPtr--];
int type=Value.BOOLEAN; // most common
boolean equal=false;
int operator=(equalExpression.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
switch (operator)
{
case OperatorIds.EQUAL_EQUAL:
case OperatorIds.NOT_EQUAL:
switch (value1.type)
{
case Value.NUMBER:
case Value.BOOLEAN:
equal=value1.numberValue()==value2.numberValue(); break;
case Value.STRING:
if (value2.type==Value.NUMBER)
equal=value1.numberValue()==value2.numberValue();
else
equal=value1.stringValue().equals(value2.stringValue());
break;
case Value.UNDEFINED:
case Value.NULL:
equal=(value2.type==Value.UNDEFINED || value2.type==Value.NULL); break;
default:
equal=(value1.objValue==value2.objValue);
}
if (operator==OperatorIds.NOT_EQUAL)
equal = !equal;
break;
case OperatorIds.EQUAL_EQUAL_EQUAL:
case OperatorIds.NOT_EQUAL_EQUAL:
if (value1.type==value2.type)
{
switch (value1.type)
{
case Value.NUMBER:
case Value.BOOLEAN:
equal=value1.value==value2.value; break;
case Value.STRING:
equal=value1.stringValue().equals(value2.stringValue());
break;
case Value.UNDEFINED:
case Value.NULL:
equal=true; break;
default:
equal=(value1.objValue==value2.objValue);
}
}
if (operator==OperatorIds.NOT_EQUAL_EQUAL)
equal = !equal;
break;
default:
throw new UnimplementedException(""); //$NON-NLS-1$
}
pushValue(type,equal? 1:0,null);
}
public boolean visit(PostfixExpression postfixExpression, BlockScope scope) {
ExprStackItem value= execute(postfixExpression.lhs);
if (value.reference==null)
throw new InterpretException("invalid assigment left hand side"); //$NON-NLS-1$
int number=value.numberValue();
int orgNumber=number;
switch (postfixExpression.operator) {
case OperatorIds.PLUS :
number++; //$NON-NLS-1$
break;
case OperatorIds.MINUS :
number++; //$NON-NLS-1$
break;
}
Value newValue = new NumberValue(number);
value.reference.setValue(value.referenceName, newValue);
pushNumber(orgNumber);
return false;
}
public boolean visit(PrefixExpression prefixExpression, BlockScope scope) {
ExprStackItem value= execute(prefixExpression.lhs);
if (value.reference==null)
throw new InterpretException("invalid assigment left hand side"); //$NON-NLS-1$
int number=value.numberValue();
switch (prefixExpression.operator) {
case OperatorIds.PLUS :
number++; //$NON-NLS-1$
break;
case OperatorIds.MINUS :
number++; //$NON-NLS-1$
break;
}
Value newValue = new NumberValue(number);
value.reference.setValue(value.referenceName, newValue);
pushNumber(number);
return false;
}
private void pushValue(int type,int value,Object objValue)
{
if (++stackPtr >=stack.length)
{
ExprStackItem []newStack=new ExprStackItem[stack.length*2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
for (int i=stack.length;i<newStack.length;i++)
newStack[i]=new ExprStackItem();
stack=newStack;
}
stack[stackPtr].type=type;
stack[stackPtr].value=value;
stack[stackPtr].objValue=objValue;
}
private void pushNumber(int number)
{
pushValue(Value.NUMBER,number,null);
}
private void pushString (String string)
{
pushValue(Value.STRING,0,string);
}
private void pushExprStackItem(ExprStackItem item)
{
pushValue(item.type,item.value,item.objValue);
}
private void pushReference(ValueReference reference,char[] name,Value value)
{
pushValue(value,false);
stack[stackPtr].reference=reference;
stack[stackPtr].referenceName=name;
}
private void pushValue(Value value,boolean allowUndefined) {
int type=0;
int intValue=0;
Object objValue=null;
if (value!=null && (value.type!=Value.UNDEFINED || allowUndefined))
{
type=value.type;
switch (type)
{
case Value.BOOLEAN:
case Value.NUMBER:
intValue=value.numberValue(); break;
case Value.STRING:
objValue=value.stringValue(); break;
default:
objValue=value;
}
}
pushValue(type,intValue,objValue);
}
public boolean visit(IntLiteral intLiteral, BlockScope scope) {
int value=(intLiteral.source==null)? intLiteral.value : Integer.valueOf(new String(intLiteral.source)).intValue();
pushNumber(value);
return true;
}
public boolean visit(SingleNameReference singleNameReference, BlockScope scope) {
char [] name=singleNameReference.token;
Value value=this.context.getValue(name);
pushReference(this.context.lastReference, name, value);
return true;
}
public boolean visit(SingleNameReference singleNameReference, ClassScope scope) {
return visit(singleNameReference,(BlockScope) null);
}
public void endVisit(Assignment assignment, BlockScope scope) {
ExprStackItem assignValue= stack[stackPtr--];
ExprStackItem refValue= stack[stackPtr--];
if (refValue.reference==null)
throw new InterpretException("invalid assigment left hand side"); //$NON-NLS-1$
refValue.reference.setValue(refValue.referenceName, assignValue.getValue());
pushExprStackItem(assignValue);
}
public void endVisit(ObjectLiteralField field, BlockScope scope) {
ExprStackItem fieldValue= stack[stackPtr--];
--stackPtr;//name
ObjectValue object=(ObjectValue)stack[stackPtr].objValue;
char [] name=null;
if (field.fieldName instanceof SingleNameReference)
name=((SingleNameReference)field.fieldName).token;
else if (field.fieldName instanceof StringLiteral)
name=((StringLiteral)field.fieldName).source();
else
throw new InterpretException("invalid object literal field"); //$NON-NLS-1$
object.setValue(name, fieldValue);
}
public boolean visit(ObjectLiteral literal, BlockScope scope) {
pushValue(Value.OBJECT, 0, new ObjectValue());
return true;
}
protected ExprStackItem execute(Expression expr)
{
expr.traverse(this,(BlockScope) null);
ExprStackItem value= stack[stackPtr--];
return value;
}
public boolean visit(FieldReference fieldReference, BlockScope scope) {
Value receiver=execute(fieldReference.receiver);
ObjectValue object=receiver.getObjectValue();
pushReference(object,fieldReference.token,object.getValue(fieldReference.token));
return false;
}
public boolean visit(ThisReference thisReference, BlockScope scope) {
ObjectValue value=null;
if (this.context.thisObject instanceof ObjectValue)
value=(ObjectValue)this.context.thisObject;
pushValue(value, false);
return false;
}
public boolean visit(MethodDeclaration methodDeclaration, Scope scope) {
FunctionValue func=new FunctionValue(methodDeclaration);
this.context.setValue(methodDeclaration.selector, func);
return false;
}
public boolean visit(FunctionExpression functionExpression, BlockScope scope) {
FunctionValue func=new FunctionValue(functionExpression.methodDeclaration);
pushValue(func, false);
return false;
}
public boolean visit(MessageSend messageSend, BlockScope scope) {
FunctionValue function=null;
ValueReference receiverObj=this.context;
Value receiver=null;
if (messageSend.receiver!=null)
{
receiver=execute(messageSend.receiver);
if (receiver.type==Value.FUNCTION && messageSend.selector==null)
function=(FunctionValue) receiver.valueObject();
else
receiverObj=receiver.getObjectValue();
}
if (function==null)
{
receiver=receiverObj.getValue(messageSend.selector);
if (receiver.type==Value.FUNCTION)
function=(FunctionValue)receiver;
else
throw new InterpretException("not a function:"+new String((messageSend.selector))); //$NON-NLS-1$
}
int restoreStackPtr=this.stackPtr;
Value [] arguments;
if (messageSend.arguments!=null)
{
arguments= new Value[messageSend.arguments.length];
for (int i = 0; i < arguments.length; i++) {
arguments[i]=execute(messageSend.arguments[i]);
this.stackPtr++;
}
}
else
arguments=new Value[0];
if (!(receiverObj instanceof ObjectValue))
receiverObj=null;
Value returnValue=function.execute(this, (ObjectValue)receiverObj, arguments);
this.stackPtr=restoreStackPtr;
pushValue(returnValue,true);
return false;
}
protected int execBlock(ProgramElement[] statements) {
this.context.returnCode=0;
for (int i = 0; i < statements.length && this.context.returnCode==0; i++) {
statements[i].traverse(this, null);
}
return this.context.returnCode;
}
protected int execStatement(ProgramElement statement) {
this.context.returnCode=0;
if (statement==null)
return 0;
statement.traverse(this, null);
return this.context.returnCode;
}
public boolean visit(AllocationExpression allocationExpression, BlockScope scope) {
Value value = execute(allocationExpression.member);
if (value.type!=Value.FUNCTION)
throw new InterpretException("not a function:"+allocationExpression.member.toString()); //$NON-NLS-1$
FunctionValue function= (FunctionValue) value.valueObject();
ObjectValue receiver = new ObjectValue(function.prototype);
int restoreStackPtr=this.stackPtr;
Value [] arguments;
if (!allocationExpression.isShort && allocationExpression.arguments!=null)
{
arguments= new Value[allocationExpression.arguments.length];
for (int i = 0; i < arguments.length; i++) {
arguments[i]=execute(allocationExpression.arguments[i]);
this.stackPtr++;
}
}
else
arguments= new Value[0];
function.execute(this, receiver, arguments);
this.stackPtr=restoreStackPtr;
pushValue(receiver,false);
return false;
}
public boolean visit(FalseLiteral falseLiteral, BlockScope scope) {
pushValue(Value.BOOLEAN, 0, null);
return false;
}
public boolean visit(NullLiteral nullLiteral, BlockScope scope) {
pushValue(Value.NULL, 1, Value.NullObjectValue);
return false;
}
public boolean visit(UndefinedLiteral undefined, BlockScope scope) {
pushValue(Value.UNDEFINED, 1, Value.UndefinedObjectValue);
return false;
}
public boolean visit(StringLiteral stringLiteral, BlockScope scope) {
pushString(new String(stringLiteral.source()));
return false;
}
public boolean visit(TrueLiteral trueLiteral, BlockScope scope) {
pushValue(Value.BOOLEAN, 1, null);
return false;
}
public boolean visit(ReturnStatement returnStatement, BlockScope scope) {
Value returnValue=Value.UndefinedObjectValue;
if (returnStatement.expression!=null)
returnValue = execute(returnStatement.expression);
this.context.returnValue=returnValue;
this.context.returnCode=STOP_RETURN;
return false;
}
public boolean visit(LocalDeclaration localDeclaration, BlockScope scope) {
Value value = Value.UndefinedObjectValue;
if (localDeclaration.initialization!=null)
value=execute(localDeclaration.initialization);
this.context.setValue(localDeclaration.name, value.getValue());
return false;
}
public boolean visit(Block block, BlockScope scope) {
execBlock(block.statements);
return false;
}
public boolean visit(IfStatement ifStatement, BlockScope scope) {
Value condition=execute(ifStatement.condition);
if (condition.booleanValue())
execStatement(ifStatement.thenStatement);
else
execStatement(ifStatement.elseStatement);
return false;
}
public boolean visit(WhileStatement whileStatement, BlockScope scope) {
while (true)
{
Value condition=execute(whileStatement.condition);
if (condition.booleanValue())
{
int returnCode=execStatement(whileStatement.action);
if (returnCode!=0 && returnCode!=STOP_CONTINUE)
return false;
}
else
return false;
}
}
public boolean visit(BreakStatement breakStatement, BlockScope scope) {
this.context.returnCode=STOP_BREAK;
return false;
}
public boolean visit(ContinueStatement continueStatement, BlockScope scope) {
this.context.returnCode=STOP_CONTINUE;
return false;
}
protected InterpreterContext newContext(InterpreterContext parent,ObjectValue thisObject, ProgramElement method)
{
return new InterpreterContext( parent, thisObject);
}
public void restorePreviousContext() {
this.context=this.context.parent;
}
}