/*
* $Id: AbstractInstruction.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 java.util.ArrayList;
import java.util.List;
import org.zmpp.vm.Cpu;
import org.zmpp.vm.Instruction;
import org.zmpp.vm.Machine;
import org.zmpp.vm.PortableGameState;
import org.zmpp.vm.RoutineContext;
import org.zmpp.vm.ScreenModel6;
import org.zmpp.vm.Window6;
/**
* This class represents can be considered as a mutable value object, which
* basically stores an instruction's information in order to restrict the
* Instruction class's responsibility to executing logic.
*
* This information will be incrementally added by the decoder, therefore there
* are setter methods to add information.
*
* @author Wei-ju Wu
* @version 1.0
*/
public abstract class AbstractInstruction implements Instruction {
/**
* The constant for false.
*/
public static final short FALSE = 0;
/**
* The constant for true.
*/
public static final short TRUE = 1;
/**
* The constant for true from restore.
*/
public static final short RESTORE_TRUE = 2;
/**
* The available instruction forms.
*/
public enum InstructionForm {
LONG, SHORT, VARIABLE
}
/**
* The available operand count types.
*/
public enum OperandCount {
C0OP, C1OP, C2OP, VAR, EXT
}
/**
* This is the result of an instruction.
*/
public static class InstructionResult {
private short value;
private boolean branchCondition;
public InstructionResult(short value, boolean branchCondition) {
this.value = value;
this.branchCondition = branchCondition;
}
public short getValue() {
return value;
}
public boolean getBranchCondition() {
return branchCondition;
}
}
private int opcode;
private List<Operand> operands;
private short storeVariable;
private boolean branchIfConditionTrue;
private short branchOffset;
private int length;
private Machine machine;
/**
* Constructor.
*
* @param machine a reference to the machine state
* @param opcode the opcode
*/
public AbstractInstruction(final Machine machine, final int opcode) {
this.opcode = opcode;
this.machine = machine;
this.operands = new ArrayList<Operand>();
this.branchIfConditionTrue = true;
}
/**
* Returns the reference to the machine state.
*
* @return the machine state
*/
protected Machine getMachine() {
return machine;
}
/**
* Returns the reference to the Cpu object.
*
* @return the Cpu object
*/
protected Cpu getCpu() {
return machine.getCpu();
}
/**
* Returns the instruction's opcode.
*
* @return the opcode
*/
public int getOpcode() {
return opcode;
}
/**
* Returns the instruction's form.
*
* @return the instruction form
*/
public abstract InstructionForm getInstructionForm();
/**
* Returns the instruction's operand count type.
*
* @return the operand count type
*/
public abstract OperandCount getOperandCount();
/**
* Returns the instruction's static info object.
*
* @return the static info object
*/
protected abstract InstructionStaticInfo getStaticInfo();
/**
* Returns the operand at the specified position.
*
* @param operandNum the operand number, starting with 0 as the first
* operand.
* @return the specified operand
*/
public Operand getOperand(final int operandNum) {
return operands.get(operandNum);
}
/**
* Returns the story file version.
*
* @return the story file version
*/
protected int getStoryFileVersion() {
return machine.getVersion();
}
/**
* Returns the number of operands.
*
* @return the number of operands
*/
public int getNumOperands() {
return operands.size();
}
/**
* Returns the instruction's store variable.
*
* @return the store variable
*/
public short getStoreVariable() {
return storeVariable;
}
/**
* Returns the branch offset.
*
* @return the branch offset
*/
public short getBranchOffset() {
return branchOffset;
}
/**
* Returns the instruction's length in bytes.
*
* @return the instruction length
*/
public int getLength() {
return length;
}
/**
* Sets the instruction's opcode.
*
* @param opcode the opcode
*/
public void setOpcode(final int opcode) {
this.opcode = opcode;
}
/**
* Adds an operand to this object.
*
* @param operand the operand to add
*/
public void addOperand(final Operand operand) {
this.operands.add(operand);
}
/**
* Sets the store variable.
*
* @param var the store variable
*/
public void setStoreVariable(final short var) {
this.storeVariable = var;
}
/**
* Sets the branch offset.
*
* @param offset the branch offset
*/
public void setBranchOffset(final short offset) {
this.branchOffset = offset;
}
/**
* Sets the branch if condition true flag.
*
* @param flag the branch if condition true flag
*/
public void setBranchIfTrue(final boolean flag) {
branchIfConditionTrue = flag;
}
/**
* Sets the instruction's length in bytes.
*
* @param length the length in bytes
*/
public void setLength(final int length) {
this.length = length;
}
/**
* Returns true, if this instruction stores a result, false, otherwise.
*
* @return true if a result is stored, false otherwise
*/
public boolean storesResult() {
return getStaticInfo().storesResult(getOpcode(), getStoryFileVersion());
}
/**
* {@inheritDoc}
*/
public boolean isOutput() {
return getStaticInfo().isOutput(getOpcode(), getStoryFileVersion());
}
/**
* Returns true, if this instruction is a branch, false, otherwise.
*
* @return true if branch, false otherwise
*/
public boolean isBranch() {
return getStaticInfo().isBranch(getOpcode(), getStoryFileVersion());
}
/**
* Returns true if this is a branch condition and the branch is executed if
* the test condition is true, false otherwise.
*
* @return true if the branch is executed on a true test condition
*/
public boolean branchIfTrue() {
return branchIfConditionTrue;
}
/**
* Converts the specified value into a signed value, depending on the type
* of the operand. If the operand is LARGE_CONSTANT or VARIABLE, the value
* is treated as a 16 bit signed integer, if it is SMALL_CONSTANT, it is
* treated as an 8 bit signed integer.
*
* @param operandNum the operand number
* @return a signed value
*/
public short getValue(final int operandNum) {
final Operand operand = getOperand(operandNum);
switch (operand.getType()) {
case VARIABLE:
return getCpu().getVariable(operand.getValue());
case SMALL_CONSTANT:
case LARGE_CONSTANT:
default:
return operand.getValue();
}
}
/**
* Retrieves the value of the specified operand as an unsigned 16 bit
* integer.
*
* @param operandNum the operand number
* @return the value
*/
public int getUnsignedValue(final int operandNum) {
final short signedValue = getValue(operandNum);
return signedValue & 0xffff;
}
/**
* Stores the specified value in the result variable.
*
* @param value the value to store
*/
protected void storeResult(final short value) {
getCpu().setVariable(getStoreVariable(), value);
}
/**
* Halt the virtual machine with an error message about this instruction.
*/
protected void throwInvalidOpcode() {
getCpu().halt("illegal instruction, type: " + getInstructionForm()
+ " operand count: " + getOperandCount() + " opcode: " + getOpcode());
}
public void execute() {
if (isOpcodeAvailable()) {
doInstruction();
} else {
throwInvalidOpcode();
}
}
/**
* Executes the instruction and returns a result.
*/
protected abstract void doInstruction();
/**
* Checks the availability of the instruction for the current version.
*
* @return true if available, false otherwise
*/
private boolean isOpcodeAvailable() {
final int version = getStoryFileVersion();
final int[] validVersions = getStaticInfo().getValidVersions(getOpcode());
for (int validVersion : validVersions) {
if (validVersion == version) {
return true;
}
}
return false;
}
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append(getStaticInfo().getOpName(getOpcode(),
getStoryFileVersion()));
buffer.append(" ");
buffer.append(getOperandString());
if (storesResult()) {
buffer.append(" -> ");
buffer.append(getVarName(getStoreVariable()));
}
return buffer.toString();
}
private String getVarName(final int varnum) {
if (varnum == 0) {
return "(SP)";
} else if (varnum <= 15) {
return String.format("L%02x", (varnum - 1));
} else {
return String.format("G%02x", (varnum - 16));
}
}
private String getVarValue(final int varnum) {
int value = 0;
if (varnum == 0) {
value = getCpu().getStackTopElement();
} else {
value = getCpu().getVariable(varnum);
}
return String.format("$%02x", value);
}
protected String getOperandString() {
final StringBuilder buffer = new StringBuilder();
for (int i = 0; i < getNumOperands(); i++) {
if (i > 0) {
buffer.append(", ");
}
final Operand operand = getOperand(i);
switch (operand.getType()) {
case SMALL_CONSTANT:
buffer.append(String.format("$%02x", operand.getValue()));
break;
case LARGE_CONSTANT:
buffer.append(String.format("$%04x", operand.getValue()));
break;
case VARIABLE:
buffer.append(getVarName(operand.getValue()));
buffer.append("[");
buffer.append(getVarValue(operand.getValue()));
buffer.append("]");
default:
break;
}
}
return buffer.toString();
}
// *********************************************************************
// ******** Program flow control
// ***********************************
/**
* Advances the program counter to the next instruction.
*/
protected void nextInstruction() {
getCpu().incrementProgramCounter(getLength());
}
/**
* Performs a branch, depending on the state of the condition flag. If
* branchIfConditionTrue is true, the branch will be performed if condition
* is true, if branchIfCondition is false, the branch will be performed if
* condition is false.
*
* @param condition the test condition
*/
protected void branchOnTest(final boolean condition) {
final boolean test = branchIfConditionTrue ? condition : !condition;
if (test) {
applyBranch();
} else {
nextInstruction();
}
}
/**
* Applies a jump by applying the branch formula on the pc given the
* specified offset.
*
* @param offset the offset
*/
private void applyBranch() {
final Cpu cpu = getCpu();
final short offset = getBranchOffset();
if (offset >= 2 || offset < 0) {
cpu.setProgramCounter(
cpu.computeBranchTarget(getBranchOffset(), getLength()));
} else {
// FALSE is defined as 0, TRUE as 1, so simply return the offset
// since we do not have negative offsets
returnFromRoutine(offset);
}
}
/**
* This function returns from the current routine, setting the return value
* into the specified return variable.
*
* @param returnValue the return value
*/
protected void returnFromRoutine(final short returnValue) {
getCpu().popRoutineContext(returnValue);
}
/**
* Calls in the Z-machine are all very similar and only differ in the number
* of arguments.
*
* @param numArgs the number of arguments
* @param discardResult whether to discard the result
*/
protected void call(final int numArgs) {
final int packedAddress = getUnsignedValue(0);
final short[] args = new short[numArgs];
for (int i = 0; i < numArgs; i++) {
args[i] = getValue(i + 1);
}
call(packedAddress, args);
}
protected void call(final int packedRoutineAddress, final short[] args) {
if (packedRoutineAddress == 0) {
if (storesResult()) {
// only if this instruction stores a result
storeResult(FALSE);
}
nextInstruction();
} else {
final Cpu cpu = getCpu();
final int returnAddress = cpu.getProgramCounter() + getLength();
final short returnVariable = storesResult() ? getStoreVariable()
: RoutineContext.DISCARD_RESULT;
cpu.call(packedRoutineAddress, returnAddress, args,
returnVariable);
}
}
protected void saveToStorage(final int pc) {
// This is a little tricky: In version 3, the program counter needs to
// point to the branch offset, and not to an instruction position
// In version 4, this points to the store variable. In both cases this
// address is the instruction address + 1
final boolean success = getMachine().save(pc);
if (getStoryFileVersion() <= 3) {
//int target = getMachine().getProgramCounter() + getLength();
//target--; // point to the previous branch offset
//boolean success = getMachine().save(target);
branchOnTest(success);
} else {
// changed behaviour in version >= 4
storeResult((short) (success ? TRUE : FALSE));
nextInstruction();
}
}
protected void restoreFromStorage() {
final PortableGameState gamestate = getMachine().restore();
if (getStoryFileVersion() <= 3) {
if (gamestate == null) {
// If failure on restore, just continue
nextInstruction();
}
} else {
// changed behaviour in version >= 4
if (gamestate == null) {
storeResult((short) FALSE);
// If failure on restore, just continue
nextInstruction();
} else {
final int storevar = gamestate.getStoreVariable(getMachine());
getCpu().setVariable(storevar, (short) RESTORE_TRUE);
}
}
}
/**
* Returns the window for a given window number.
*
* @param windownum the window number
* @return the window
*/
protected Window6 getWindow(final int windownum) {
return (windownum == ScreenModel6.CURRENT_WINDOW)
? getMachine().getScreen6().getSelectedWindow()
: getMachine().getScreen6().getWindow(windownum);
}
}