/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.format.rco.vsmx.interpreter; import java.util.Stack; import org.apache.log4j.Logger; import jpcsp.Emulator; import jpcsp.HLE.kernel.types.IAction; import jpcsp.format.rco.vsmx.VSMX; import jpcsp.format.rco.vsmx.VSMXCode; import jpcsp.format.rco.vsmx.VSMXGroup; import jpcsp.format.rco.vsmx.VSMXMem; public class VSMXInterpreter { private static final Logger log = VSMX.log; private VSMXMem mem; private int pc; private boolean exit; private Stack<VSMXBaseObject> stack; private Stack<VSMXCallState> callStates; private VSMXCallState callState; private VSMXObject globalVariables; private String prefix; private String name; private class InterpretFunctionAction implements IAction { private VSMXFunction function; private VSMXBaseObject object; private VSMXBaseObject[] arguments; public InterpretFunctionAction(VSMXFunction function, VSMXBaseObject object, VSMXBaseObject[] arguments) { this.function = function; this.object = object; this.arguments = arguments; } @Override public void execute() { interpretFunction(function, object, arguments); } } public VSMXInterpreter() { } public void setVSMX(VSMX vsmx) { mem = vsmx.getMem(); name = vsmx.getName(); } public VSMXObject getGlobalVariables() { return globalVariables; } private VSMXBaseObject[] popValues(int n) { VSMXBaseObject[] values = new VSMXBaseObject[n]; for (int i = n - 1; i >= 0; i--) { values[i] = stack.pop().getValue(); } return values; } private void pushCallState(VSMXBaseObject thisObject, int numberOfLocalVariables, boolean returnThis, boolean exitAfterCall) { if (callState != null) { callStates.push(callState); } callState = new VSMXCallState(thisObject, numberOfLocalVariables, pc, returnThis, exitAfterCall); stack = callState.getStack(); prefix += " "; } private void popCallState() { if (callStates.isEmpty()) { callState = null; stack = null; prefix = ""; } else { callState = callStates.pop(); stack = callState.getStack(); prefix = prefix.substring(0, prefix.length() - 2); } } private void interpret(VSMXGroup code) { VSMXBaseObject o1, o2, o3, o, r; VSMXBaseObject arguments[]; float f1, f2, f; String s1, s2, s; int i1, i2, i; boolean b; switch (code.getOpcode()) { case VSMXCode.VID_NOTHING: break; case VSMXCode.VID_OPERATOR_ASSIGN: o1 = stack.pop().getValue(); o2 = stack.pop(); if (o2 instanceof VSMXReference) { if (log.isTraceEnabled()) { log.trace(String.format("%s = %s", o2, o1)); } ((VSMXReference) o2).assign(o1); stack.push(o1); } else { log.warn(String.format("Line#%d non-ref assignment %s", pc - 1, code)); } break; case VSMXCode.VID_OPERATOR_ADD: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); if (o1 instanceof VSMXString || o2 instanceof VSMXString) { s1 = o1.getStringValue(); s2 = o2.getStringValue(); s = s2 + s1; stack.push(new VSMXString(this, s)); } else { f1 = o1.getFloatValue(); f2 = o2.getFloatValue(); f = f2 + f1; stack.push(new VSMXNumber(this, f)); } break; case VSMXCode.VID_OPERATOR_SUBTRACT: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); f = f2 - f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_MULTIPLY: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); f = f2 * f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_DIVIDE: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); f = f2 / f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_MOD: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); f = f2 % f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_POSITIVE: f1 = stack.pop().getFloatValue(); f = f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_NEGATE: f1 = stack.pop().getFloatValue(); f = -f1; stack.push(new VSMXNumber(this, f)); break; case VSMXCode.VID_OPERATOR_NOT: b = stack.pop().getBooleanValue(); b = !b; stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_P_INCREMENT: o = stack.pop(); f = o.getFloatValue(); f += 1f; stack.push(new VSMXNumber(this, f)); if (o instanceof VSMXReference) { ((VSMXReference) o).assign(new VSMXNumber(this, f)); } else { log.warn(String.format("Line#%d non-ref increment %s", pc - 1, code)); } break; case VSMXCode.VID_P_DECREMENT: o = stack.pop(); f = o.getFloatValue(); f -= 1f; stack.push(new VSMXNumber(this, f)); if (o instanceof VSMXReference) { ((VSMXReference) o).assign(new VSMXNumber(this, f)); } else { log.warn(String.format("Line#%d non-ref increment %s", pc - 1, code)); } break; case VSMXCode.VID_INCREMENT: o = stack.pop(); f = o.getFloatValue(); stack.push(new VSMXNumber(this, f)); if (o instanceof VSMXReference) { ((VSMXReference) o).assign(new VSMXNumber(this, f + 1f)); } else { log.warn(String.format("Line#%d non-ref increment %s", pc - 1, code)); } break; case VSMXCode.VID_DECREMENT: o = stack.pop(); f = o.getFloatValue(); stack.push(new VSMXNumber(this, f)); if (o instanceof VSMXReference) { ((VSMXReference) o).assign(new VSMXNumber(this, f - 1f)); } else { log.warn(String.format("Line#%d non-ref decrement %s", pc - 1, code)); } break; case VSMXCode.VID_OPERATOR_EQUAL: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); b = o1.equals(o2); if (log.isTraceEnabled()) { log.trace(String.format("%s == %s: %b", o1, o2, b)); } stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_NOT_EQUAL: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); b = !o1.equals(o2); if (log.isTraceEnabled()) { log.trace(String.format("%s != %s: %b", o1, o2, b)); } stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_IDENTITY: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); b = o1.identity(o2); stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_NON_IDENTITY: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); b = !o1.identity(o2); stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_LT: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); b = f2 < f1; stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_LTE: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); b = f2 <= f1; stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_GTE: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); b = f2 >= f1; stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_GT: f1 = stack.pop().getFloatValue(); f2 = stack.pop().getFloatValue(); b = f2 > f1; stack.push(VSMXBoolean.getValue(b)); break; case VSMXCode.VID_OPERATOR_INSTANCEOF: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_OPERATOR_IN: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_OPERATOR_TYPEOF: o = stack.pop().getValue(); String typeOf = o.typeOf(); stack.push(new VSMXString(this, typeOf)); break; case VSMXCode.VID_OPERATOR_B_AND: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i1 & i2; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_B_XOR: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i1 ^ i2; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_B_OR: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i1 | i2; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_B_NOT: i1 = stack.pop().getIntValue(); i = ~i1; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_LSHIFT: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i2 << i1; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_RSHIFT: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i2 >> i1; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_OPERATOR_URSHIFT: i1 = stack.pop().getIntValue(); i2 = stack.pop().getIntValue(); i = i2 >>> i1; stack.push(new VSMXNumber(this, i)); break; case VSMXCode.VID_STACK_COPY: o1 = stack.peek(); stack.push(o1); break; case VSMXCode.VID_STACK_SWAP: o1 = stack.pop(); o2 = stack.pop(); stack.push(o1); stack.push(o2); break; case VSMXCode.VID_END_STMT: stack.clear(); break; case VSMXCode.VID_CONST_NULL: stack.push(VSMXNull.singleton); break; case VSMXCode.VID_CONST_EMPTYARRAY: o = new VSMXArray(this); stack.push(o); break; case VSMXCode.VID_CONST_BOOL: stack.push(VSMXBoolean.getValue(code.value)); break; case VSMXCode.VID_CONST_INT: stack.push(new VSMXNumber(this, code.value)); break; case VSMXCode.VID_CONST_FLOAT: stack.push(new VSMXNumber(this, code.getFloatValue())); break; case VSMXCode.VID_CONST_STRING: stack.push(new VSMXString(this, mem.texts[code.value])); break; case VSMXCode.VID_CONST_OBJECT: break; case VSMXCode.VID_FUNCTION: stack.push(new VSMXFunction(this, (code.id >> 8) & 0xFF, (code.id >> 24) & 0xFF, code.value)); break; case VSMXCode.VID_ARRAY: stack.push(new VSMXArray(this)); break; case VSMXCode.VID_THIS: stack.push(callState.getThisObject()); break; case VSMXCode.VID_UNNAMED_VAR: stack.push(new VSMXLocalVarReference(this, callState, code.value)); break; case VSMXCode.VID_VARIABLE: stack.push(new VSMXReference(this, globalVariables, mem.names[code.value])); if (log.isTraceEnabled()) { log.trace(String.format("%s '%s'", VSMXCode.VsmxDecOps[code.getOpcode()], mem.names[code.value])); } break; case VSMXCode.VID_PROPERTY: o = stack.pop().getValue(); if (o instanceof VSMXObject) { stack.push(new VSMXReference(this, (VSMXObject) o, mem.properties[code.value])); if (log.isTraceEnabled()) { log.trace(String.format("%s '%s': %s", VSMXCode.VsmxDecOps[code.getOpcode()], mem.properties[code.value], stack.peek())); } } else { stack.push(o.getPropertyValue(mem.properties[code.value])); } break; case VSMXCode.VID_METHOD: o = stack.pop().getValue(); stack.push(new VSMXMethod(this, o, mem.properties[code.value])); if (log.isTraceEnabled()) { log.trace(String.format("%s '%s'", VSMXCode.VsmxDecOps[code.getOpcode()], mem.properties[code.value])); } break; case VSMXCode.VID_SET_ATTR: o1 = stack.pop().getValue(); o2 = stack.pop(); o2.setPropertyValue(mem.properties[code.value], o1); if (log.isTraceEnabled()) { log.trace(String.format("%s %s.%s = %s", VSMXCode.VsmxDecOps[code.getOpcode()], o2, mem.properties[code.value], o1)); } break; case VSMXCode.VID_UNSET: o1 = stack.pop(); o1.deletePropertyValue(mem.properties[code.value]); break; case VSMXCode.VID_OBJ_ADD_ATTR: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_ARRAY_INDEX: o1 = stack.pop(); o2 = stack.pop().getValue(); if (o2 instanceof VSMXArray) { o = new VSMXReference(this, (VSMXObject) o2, o1.getIntValue()); if (log.isTraceEnabled()) { log.trace(String.format("%s VSMXArray %s[%d] = %s", VSMXCode.VsmxDecOps[code.getOpcode()], o2, o1.getIntValue(), o)); } } else if (o2 instanceof VSMXObject) { o = new VSMXReference(this, (VSMXObject) o2, o1.getStringValue()); if (log.isTraceEnabled()) { log.trace(String.format("%s VSMXObject %s[%s] = %s", VSMXCode.VsmxDecOps[code.getOpcode()], o2, o1.getStringValue(), o)); } } else { o = o2.getPropertyValue(o1.getStringValue()); if (log.isTraceEnabled()) { log.trace(String.format("%s %s[%s] = %s", VSMXCode.VsmxDecOps[code.getOpcode()], o2, o1.getStringValue(), o)); } } stack.push(o); break; case VSMXCode.VID_ARRAY_INDEX_KEEP_OBJ: o1 = stack.pop(); o2 = stack.peek().getValue(); if (o2 instanceof VSMXArray) { o = o2.getPropertyValue(o1.getIntValue()); } else { o = o2.getPropertyValue(o1.getStringValue()); } stack.push(o); break; case VSMXCode.VID_ARRAY_INDEX_ASSIGN: o1 = stack.pop().getValue(); o2 = stack.pop(); o3 = stack.pop().getValue(); if (o3 instanceof VSMXArray) { o3.setPropertyValue(o2.getIntValue(), o1); } else { log.warn(String.format("Line#%d non-array index assignment %s", pc - 1, code)); } break; case VSMXCode.VID_ARRAY_DELETE: o1 = stack.pop(); o2 = stack.pop().getValue(); if (o2 instanceof VSMXArray) { o2.deletePropertyValue(o1.getIntValue()); } else { log.warn(String.format("Line#%d non-array delete %s", pc - 1, code)); } break; case VSMXCode.VID_ARRAY_PUSH: o1 = stack.pop().getValue(); o2 = stack.pop().getValue(); if (o2 instanceof VSMXArray) { int length = ((VSMXArray) o2).getLength(); o2.setPropertyValue(length, o1); stack.push(o2); } else { log.warn(String.format("Line#%d non-array push %s", pc - 1, code)); } break; case VSMXCode.VID_JUMP: pc = code.value; break; case VSMXCode.VID_JUMP_TRUE: o1 = stack.pop(); b = o1.getBooleanValue(); if (b) { pc = code.value; } break; case VSMXCode.VID_JUMP_FALSE: o1 = stack.pop(); b = !o1.getBooleanValue(); if (b) { pc = code.value; } break; case VSMXCode.VID_CALL_FUNC: arguments = popValues(code.value); o = stack.pop().getValueWithArguments(code.value); if (o instanceof VSMXFunction) { VSMXFunction function = (VSMXFunction) o; callFunction(function, VSMXNull.singleton, arguments, code.value, false); } else { stack.push(VSMXNull.singleton); log.warn(String.format("Line#%d non-function call %s", pc - 1, code)); } break; case VSMXCode.VID_CALL_METHOD: arguments = popValues(code.value); o = stack.pop().getValueWithArguments(code.value); if (o instanceof VSMXMethod) { VSMXMethod method = (VSMXMethod) o; VSMXFunction function = method.getFunction(code.value, arguments); if (function == null) { stack.push(VSMXNull.singleton); log.warn(String.format("Line#%d non existing method %s()", pc - 1, method.getName())); } else { callFunction(function, method.getThisObject(), method.getArguments(), method.getNumberOfArguments(), false); } } else if (o instanceof VSMXFunction) { VSMXFunction function = (VSMXFunction) o; o = stack.pop().getValue(); callFunction(function, o, arguments, code.value, false); } else { stack.push(VSMXNull.singleton); log.warn(String.format("Line#%d non-method call %s", pc - 1, code)); } break; case VSMXCode.VID_CALL_NEW: arguments = popValues(code.value); r = stack.pop(); o = r.getValue(); if (o instanceof VSMXArray) { if (code.value == 0) { stack.push(new VSMXArray(this)); } else if (code.value == 1) { stack.push(new VSMXArray(this, arguments[0].getIntValue())); } else { log.warn(String.format("Line#%d wrong number of arguments for new Array %s", pc - 1, code)); } } else if (o instanceof VSMXFunction) { VSMXFunction function = (VSMXFunction) o; String className = null; if (r instanceof VSMXReference) { className = ((VSMXReference) r).getRefProperty(); } VSMXObject thisObject = new VSMXObject(this, className); callFunction(function, thisObject, arguments, code.value, true); } else if (o instanceof VSMXObject) { if (code.value == 0) { stack.push(new VSMXObject(this, null)); } else { log.warn(String.format("Line#%d wrong number of arguments for new Object %s", pc - 1, code)); } } else { stack.push(new VSMXArray(this)); log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); } break; case VSMXCode.VID_RETURN: o = stack.pop().getValue(); if (callState.getReturnThis()) { o = callState.getThisObject(); } pc = callState.getReturnPc(); if (callState.getExitAfterCall()) { exit = true; } popCallState(); if (callState == null) { exit = true; } else { stack.push(o); } break; case VSMXCode.VID_THROW: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_TRY_BLOCK_IN: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_TRY_BLOCK_OUT: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_CATCH_FINALLY_BLOCK_IN: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_CATCH_FINALLY_BLOCK_OUT: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; case VSMXCode.VID_END: exit = true; break; case VSMXCode.VID_DEBUG_FILE: if (log.isDebugEnabled()) { log.debug(String.format("debug file '%s'", mem.texts[code.value])); } break; case VSMXCode.VID_DEBUG_LINE: if (log.isDebugEnabled()) { log.debug(String.format("debug line %d", code.value)); } break; case VSMXCode.VID_MAKE_FLOAT_ARRAY: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; default: log.warn(String.format("Line#%d unimplemented %s", pc - 1, code)); break; } } private void interpret() { exit = false; while (!exit) { VSMXGroup code = mem.codes[pc]; if (log.isTraceEnabled()) { log.trace(String.format("%sInterpret Line#%d: %s", prefix, pc, code)); } pc++; interpret(code); } exit = false; } public synchronized void run(VSMXObject globalVariables) { prefix = ""; pc = 0; exit = false; callStates = new Stack<VSMXCallState>(); pushCallState(VSMXNull.singleton, 0, false, true); this.globalVariables = globalVariables; VSMXBoolean.init(this); interpret(); callStates.clear(); callState = null; prefix = ""; if (log.isTraceEnabled()) { log.trace(String.format("Global variables after run(): %s", globalVariables)); } } private void callFunction(VSMXFunction function, VSMXBaseObject thisObject, VSMXBaseObject[] arguments, int numberArguments, boolean returnThis) { pushCallState(thisObject, function.getLocalVars() + function.getArgs(), returnThis, false); for (int i = 1; i <= function.getArgs() && i <= numberArguments; i++) { callState.setLocalVar(i, arguments[i - 1]); } function.call(callState); int startLine = function.getStartLine(); if (startLine >= 0 && startLine < mem.codes.length) { pc = startLine; } else { popCallState(); VSMXBaseObject returnValue = function.getReturnValue(); if (returnThis) { stack.push(thisObject); } else if (returnValue != null) { stack.push(returnValue); } } } public synchronized void interpretFunction(VSMXFunction function, VSMXBaseObject object, VSMXBaseObject[] arguments) { pushCallState(object, function.getLocalVars(), false, true); for (int i = 1; i <= function.getArgs(); i++) { if (arguments == null || i > arguments.length) { callState.setLocalVar(i, VSMXNull.singleton); } else { callState.setLocalVar(i, arguments[i - 1]); } } pc = function.getStartLine(); interpret(); } public synchronized void delayInterpretFunction(VSMXFunction function, VSMXBaseObject object, VSMXBaseObject[] arguments) { IAction action = new InterpretFunctionAction(function, object, arguments); Emulator.getScheduler().addAction(action); } public synchronized void interpretScript(VSMXBaseObject object, String script) { if (log.isDebugEnabled()) { log.debug(String.format("interpretScript %s on %s", script, object)); } if (script == null) { return; } if (object == null) { object = VSMXNull.singleton; } String scriptPrefix = String.format("script:/%s/", name); if (script.startsWith(scriptPrefix)) { String functionName = script.substring(scriptPrefix.length()); VSMXBaseObject functionObject = globalVariables.getPropertyValue(functionName); if (functionObject instanceof VSMXFunction) { VSMXFunction function = (VSMXFunction) functionObject; if (log.isDebugEnabled()) { log.debug(String.format("interpretScript function=%s", function)); } interpretFunction(function, object, null); } } else { log.warn(String.format("interpretScript unknown script syntax '%s'", script)); } } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append(String.format("pc=%d", pc)); s.append(String.format(", %s", callState)); return s.toString(); } }