/*
* $Id: LongInstruction.java 536 2008-02-19 06:03:27Z weiju $
*
* Created on 10/03/2005
* Copyright 2005-2008 by Wei-ju Wu
* This file is part of The Z-machine Preservation Project (ZMPP).
*
* ZMPP 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.
*
* ZMPP 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 ZMPP. If not, see <http://www.gnu.org/licenses/>.
*/
package org.zmpp.instructions;
import org.zmpp.base.Memory;
import org.zmpp.vm.Machine;
import org.zmpp.vm.ScreenModel;
/**
* This class represents instructions of type LONG, 2OP.
*
* @author Wei-ju Wu
* @version 1.0
*/
public class LongInstruction extends AbstractInstruction {
/**
* The operand count.
*/
private OperandCount operandCount;
/**
* Constructor.
*
* @param machineState a reference to a MachineState object
* @param opcode the instruction's opcode
*/
public LongInstruction(Machine machineState, int opcode) {
super(machineState, opcode);
this.operandCount = OperandCount.C2OP;
}
/**
* Constructor.
*
* @param machineState the machine state
* @param operandCount the operand count
* @param opcode the opcode
*/
public LongInstruction(Machine machineState,
OperandCount operandCount, int opcode) {
super(machineState, opcode);
this.operandCount = operandCount;
}
/**
* {@inheritDoc}
*/
public void doInstruction() {
switch (getOpcode()) {
case LongStaticInfo.OP_JE:
je();
break;
case LongStaticInfo.OP_JL:
jl();
break;
case LongStaticInfo.OP_JG:
jg();
break;
case LongStaticInfo.OP_JIN:
jin();
break;
case LongStaticInfo.OP_DEC_CHK:
dec_chk();
break;
case LongStaticInfo.OP_INC_CHK:
inc_chk();
break;
case LongStaticInfo.OP_TEST:
test();
break;
case LongStaticInfo.OP_OR:
or();
break;
case LongStaticInfo.OP_AND:
and();
break;
case LongStaticInfo.OP_TEST_ATTR:
test_attr();
break;
case LongStaticInfo.OP_SET_ATTR:
set_attr();
break;
case LongStaticInfo.OP_CLEAR_ATTR:
clear_attr();
break;
case LongStaticInfo.OP_STORE:
store();
break;
case LongStaticInfo.OP_INSERT_OBJ:
insert_obj();
break;
case LongStaticInfo.OP_LOADW:
loadw();
break;
case LongStaticInfo.OP_LOADB:
loadb();
break;
case LongStaticInfo.OP_GET_PROP:
get_prop();
break;
case LongStaticInfo.OP_GET_PROP_ADDR:
get_prop_addr();
break;
case LongStaticInfo.OP_GET_NEXT_PROP:
get_next_prop();
break;
case LongStaticInfo.OP_ADD:
add();
break;
case LongStaticInfo.OP_SUB:
sub();
break;
case LongStaticInfo.OP_MUL:
mul();
break;
case LongStaticInfo.OP_DIV:
div();
break;
case LongStaticInfo.OP_MOD:
mod();
break;
case LongStaticInfo.OP_CALL_2S:
call(1);
break;
case LongStaticInfo.OP_CALL_2N:
call(1);
break;
case LongStaticInfo.OP_SET_COLOUR:
set_colour();
break;
case LongStaticInfo.OP_THROW:
z_throw();
break;
default:
throwInvalidOpcode();
}
}
/**
* {@inheritDoc}
*/
public InstructionForm getInstructionForm() {
return InstructionForm.LONG;
}
/**
* {@inheritDoc}
*/
public OperandCount getOperandCount() {
return operandCount;
}
/**
* {@inheritDoc}
*/
protected InstructionStaticInfo getStaticInfo() {
return LongStaticInfo.getInstance();
}
private void je() {
boolean equalsFollowing = false;
final short op1 = getValue(0);
if (getNumOperands() <= 1) {
getMachine().getCpu().halt("je expects at least two operands, only "
+ "one provided");
} else {
for (int i = 1; i < getNumOperands(); i++) {
short value = getValue(i);
//System.out.printf("Compare %d to %d\n", op1, value);
if (op1 == value) {
equalsFollowing = true;
break;
}
}
branchOnTest(equalsFollowing);
}
}
private void jl() {
final short op1 = getValue(0);
final short op2 = getValue(1);
branchOnTest(op1 < op2);
}
private void jg() {
final short op1 = getValue(0);
final short op2 = getValue(1);
branchOnTest(op1 > op2);
}
private void jin() {
final int obj1 = getUnsignedValue(0);
final int obj2 = getUnsignedValue(1);
int parentOfObj1 = 0;
if (obj1 > 0) {
parentOfObj1 = getMachine().getParent(obj1);
} else {
getMachine().warn("@jin illegal access to object " + obj1);
}
branchOnTest(parentOfObj1 == obj2);
}
private void dec_chk() {
final int varnum = getUnsignedValue(0);
final short value = getValue(1);
final short varValue = (short) (getCpu().getVariable(varnum) - 1);
getCpu().setVariable(varnum, varValue);
branchOnTest(varValue < value);
}
private void inc_chk() {
final int varnum = getUnsignedValue(0);
final short value = getValue(1);
final short varValue = (short) (getCpu().getVariable(varnum) + 1);
getCpu().setVariable(varnum, varValue);
branchOnTest(varValue > value);
}
private void test() {
final int op1 = getUnsignedValue(0);
final int op2 = getUnsignedValue(1);
branchOnTest((op1 & op2) == op2);
}
private void or() {
final int op1 = getUnsignedValue(0);
final int op2 = getUnsignedValue(1);
storeResult((short) ((op1 | op2) & 0xffff));
nextInstruction();
}
private void and() {
final int op1 = getUnsignedValue(0);
final int op2 = getUnsignedValue(1);
storeResult((short) ((op1 & op2) & 0xffff));
nextInstruction();
}
private void add() {
final short op1 = getValue(0);
final short op2 = getValue(1);
storeResult((short) (op1 + op2));
nextInstruction();
}
private void sub() {
final short op1 = getValue(0);
final short op2 = getValue(1);
storeResult((short) (op1 - op2));
nextInstruction();
}
private void mul() {
final short op1 = getValue(0);
final short op2 = getValue(1);
storeResult((short) (op1 * op2));
nextInstruction();
}
private void div() {
final short op1 = getValue(0);
final short op2 = getValue(1);
if (op2 == 0) {
getMachine().getCpu().halt("@div division by zero");
} else {
storeResult((short) (op1 / op2));
nextInstruction();
}
}
private void mod() {
final short op1 = getValue(0);
final short op2 = getValue(1);
if (op2 == 0) {
getMachine().getCpu().halt("@mod division by zero");
} else {
storeResult((short) (op1 % op2));
nextInstruction();
}
}
private void test_attr() {
final int obj = getUnsignedValue(0);
final int attr = getUnsignedValue(1);
if (obj > 0 && isValidAttribute(attr)) {
branchOnTest(getMachine().isAttributeSet(obj, attr));
} else {
getMachine().warn("@test_attr illegal access to object " + obj);
branchOnTest(false);
}
}
private void set_attr() {
final int obj = getUnsignedValue(0);
final int attr = getUnsignedValue(1);
if (obj > 0 && isValidAttribute(attr)) {
getMachine().setAttribute(obj, attr);
} else {
getMachine().warn("@set_attr illegal access to object " + obj
+ " attr: " + attr);
}
nextInstruction();
}
private void clear_attr() {
final int obj = getUnsignedValue(0);
final int attr = getUnsignedValue(1);
if (obj > 0 && isValidAttribute(attr)) {
getMachine().clearAttribute(obj, attr);
} else {
getMachine().warn("@clear_attr illegal access to object " + obj
+ " attr: " + attr);
}
nextInstruction();
}
private void store() {
final int varnum = getUnsignedValue(0);
final short value = getValue(1);
// Handle stack variable as a special case (standard 1.1)
if (varnum == 0) {
getCpu().setStackTopElement(value);
} else {
getCpu().setVariable(varnum, value);
}
nextInstruction();
}
private void insert_obj() {
final int obj = getUnsignedValue(0);
final int dest = getUnsignedValue(1);
if (obj > 0 && dest > 0) {
getMachine().insertObject(dest, obj);
} else {
getMachine().warn("@insert_obj with object 0 called, obj: " + obj
+ ", dest: " + dest);
}
nextInstruction();
}
private void loadb() {
final int arrayAddress = getUnsignedValue(0);
final int index = getUnsignedValue(1);
final Memory memory
= getMachine().getGameData().getMemory();
storeResult((short) memory.readUnsignedByte(arrayAddress + index));
nextInstruction();
}
private void loadw() {
final int arrayAddress = getUnsignedValue(0);
final int index = getUnsignedValue(1);
final Memory memory
= getMachine().getGameData().getMemory();
storeResult(memory.readShort(arrayAddress + 2 * index));
nextInstruction();
}
private void get_prop() {
final int obj = getUnsignedValue(0);
final int property = getUnsignedValue(1);
if (obj > 0) {
int value = getMachine().getProperty(obj, property);
storeResult((short) value);
} else {
getMachine().warn("@get_prop illegal access to object " + obj);
}
nextInstruction();
}
private void get_prop_addr() {
final int obj = getUnsignedValue(0);
final int property = getUnsignedValue(1);
if (obj > 0) {
int value = getMachine().getPropertyAddress(obj, property) & 0xffff;
storeResult((short) value);
} else {
getMachine().warn("@get_prop_addr illegal access to object " + obj);
}
nextInstruction();
}
private void get_next_prop() {
final int obj = getUnsignedValue(0);
final int property = getUnsignedValue(1);
short value = 0;
if (obj > 0) {
value = (short) (getMachine().getNextProperty(obj, property) & 0xffff);
storeResult(value);
nextInstruction();
} else {
// issue warning and continue
getMachine().warn("@get_next_prop illegal access to object " + obj);
nextInstruction();
}
}
private void set_colour() {
int window = ScreenModel.CURRENT_WINDOW;
if (getNumOperands() == 3) {
window = getValue(2);
}
getMachine().getScreen().setForegroundColor(getValue(0), window);
getMachine().getScreen().setBackgroundColor(getValue(1), window);
nextInstruction();
}
private void z_throw() {
final short returnValue = getValue(0);
final int stackFrame = getUnsignedValue(1);
// Unwind the stack
final int currentStackFrame = getCpu().getRoutineContexts().size() - 1;
if (currentStackFrame < stackFrame) {
getMachine().getCpu().halt("@throw from an invalid stack frame state");
} else {
// Pop off the routine contexts until the specified stack frame is
// reached
final int diff = currentStackFrame - stackFrame;
for (int i = 0; i < diff; i++) {
getCpu().popRoutineContext((short) 0);
}
// and return with the return value
returnFromRoutine(returnValue);
}
}
private boolean isValidAttribute(final int attribute) {
final int numAttr = getStoryFileVersion() <= 3 ? 32 : 48;
return attribute >= 0 && attribute < numAttr;
}
}