package edu.princeton.toy.lang; import java.lang.ref.*; import edu.princeton.toy.*; /** * TWord is the primitive value which toy registers and memory can take. TWord is immutable, like * java.lang.String, which means that classes can return TWords without being concerned with wheter * or not the recipient will change the value. The immutable feature allows us to keep track of and * reuse TWord CACHED_REFERENCES to save memory allocation overhead. * *

CACHED_REFERENCES of TWord are slightly different than short primitives in that they can take on a * special value, UNINITIALIZED_VALUE, which is identical to getWord(0) except for two things: *

* * @author btsang * @version 7.1 */ public class TWord { /** * The number of bits a TWord would take. */ public static final int BIT_COUNT = 16; /** * An array to convert an integer between 0 and 16 to a hexidecimal digit. */ public static final char HEX_DIGITS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * An array to convert an integer between 0 and 256 to a pair of hexidecimal digit. */ public static final String HEX_PAIRS[] = new String[256]; private static final Reference CACHED_REFERENCES[] = new Reference[Short.MAX_VALUE - Short.MIN_VALUE + 1]; /** * The uninitialized value is identical to getWord(0) with the following exceptions: * */ public static final TWord UNINITIALIZED_VALUE = new TWord(); /** * The minimum value a TWord can take. */ public static final TWord MIN_VALUE = new TWord(Short.MIN_VALUE); /** * The maximum value a TWord can take. */ public static final TWord MAX_VALUE = new TWord(Short.MAX_VALUE); /** * A constant for getWord((short)0). */ public static final TWord ZERO = new TWord((short)0); /** * A constant for getWord((short)0). */ public static final TWord ONE = new TWord((short)1); private short value; private boolean isInitialized; private String binaryString; private String formattedBinaryString; private String decimalString; private String hexString; private String pseudoCodeString; private String detailedString; /** * Initialize CACHED_REFERENCES[0] and CACHED_REFERENCES[Short.MAX_VALUE - Short.MIN_VALUE], * since we can't really do that practically in the declaration of the CACHED_REFERENCES. */ static { CACHED_REFERENCES[0] = new SoftReference(MIN_VALUE); CACHED_REFERENCES[0 - Short.MIN_VALUE] = new SoftReference(ZERO); CACHED_REFERENCES[1 - Short.MIN_VALUE] = new SoftReference(ONE); CACHED_REFERENCES[Short.MAX_VALUE - Short.MIN_VALUE] = new SoftReference(MAX_VALUE); for (int ctr1 = 0; ctr1 < 16; ctr1++) { String firstDigit = String.valueOf(HEX_DIGITS[ctr1]); for (int ctr2 = 0; ctr2 < 16; ctr2++) HEX_PAIRS[(ctr1 << 4) | ctr2] = firstDigit + HEX_DIGITS[ctr2]; } } /** * Constructs a TWord with the given value. */ private TWord(short value) { char op, d, s, t; op = HEX_DIGITS[(value >> 12) & 0xF]; d = HEX_DIGITS[(value >> 8) & 0xF]; s = HEX_DIGITS[(value >> 4) & 0xF]; t = HEX_DIGITS[(value ) & 0xF]; this.value = value; isInitialized = true; // Pre-calculate the representation strings StringBuffer buffer = new StringBuffer(10 * BIT_COUNT); for (int ctr = BIT_COUNT - 1; ctr >= 0; ctr--) { if (((value >> ctr) & 1) != 0) buffer.append('1'); else buffer.append('0'); } binaryString = buffer.toString(); buffer.delete(0, buffer.length()); for (int ctr = BIT_COUNT - 1; ctr >= 0; ctr--) { if (ctr != BIT_COUNT - 1 && ctr % 4 == 3) buffer.append(' '); if (((value >> ctr) & 1) != 0) buffer.append('1'); else buffer.append('0'); } formattedBinaryString = buffer.toString(); buffer.delete(0, buffer.length()); decimalString = String.valueOf(value); for (int ctr = decimalString.length(); ctr < 6; ctr++) buffer.append(' '); buffer.append(decimalString); decimalString = buffer.toString(); buffer.delete(0, buffer.length()); buffer.append(op); buffer.append(d); buffer.append(s); buffer.append(t); hexString = buffer.toString(); switch (op) { case '0': pseudoCodeString = "halt"; break; case '1': if (d == '0') pseudoCodeString = "no-op"; else if (s == '0' && d == t) pseudoCodeString = "no-op"; else if (t == '0' && d == s) pseudoCodeString = "no-op"; else if (s == '0' && t != '0') pseudoCodeString = "R[" + d + "] <- R[" + t + "]"; else if (s != '0' && t == '0') pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else if (s == '0' && t == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] + R[" + t + "]"; break; case '2': if (d == '0') pseudoCodeString = "no-op"; else if (t == '0' && d == s) pseudoCodeString = "no-op"; else if (s == '0' && t != '0') pseudoCodeString = "R[" + d + "] <- -R[" + t + "]"; else if (s != '0' && t == '0') pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else if (s == '0' && t == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] - R[" + t + "]"; break; case '3': if (d == '0') pseudoCodeString = "no-op"; else if (d == s && s == t) pseudoCodeString = "no-op"; else if (s == '0' || t == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else if (s == t) pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] & R[" + t + "]"; break; case '4': if (d == '0') pseudoCodeString = "no-op"; else if (s == '0' && t != '0') pseudoCodeString = "R[" + d + "] <- R[" + t + "]"; else if (s != '0' && t == '0') pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else if (s == '0' && t == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] ^ R[" + t + "]"; break; case '5': if (d == '0') pseudoCodeString = "no-op"; else if (t == '0' && d == s) pseudoCodeString = "no-op"; else if (s == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else if (t == '0') pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] << R[" + t + "]"; break; case '6': if (d == '0') pseudoCodeString = "no-op"; else if (t == '0' && d == s) pseudoCodeString = "no-op"; else if (s == '0') pseudoCodeString = "R[" + d + "] <- 0000"; else if (t == '0') pseudoCodeString = "R[" + d + "] <- R[" + s + "]"; else pseudoCodeString = "R[" + d + "] <- R[" + s + "] >> R[" + t + "]"; break; case '7': if (d == '0') pseudoCodeString = "no-op"; else pseudoCodeString = "R[" + d + "] <- 00" + s + t; break; case '8': if (s == 'F' && t == 'F') pseudoCodeString = "read R[" + d + "]"; else if (d == '0') pseudoCodeString = "no-op"; else pseudoCodeString = "R[" + d + "] <- M[" + s + t + "]"; break; case '9': if (s == 'F' && t == 'F') pseudoCodeString = "write R[" + d + "]"; else pseudoCodeString = "M[" + s + t + "] <- R[" + d + "]"; break; case 'A': if (d == '0') pseudoCodeString = "no-op"; else pseudoCodeString = "R[" + d + "] <- M[R[" + t + "]]"; break; case 'B': pseudoCodeString = "M[R[" + t + "]] <- R[" + d + "]"; break; case 'C': if (d == '0') pseudoCodeString = "goto " + s + t; else pseudoCodeString = "if (R[" + d + "] == 0) goto " + s + t; break; case 'D': if (d == '0') pseudoCodeString = "no-op"; else pseudoCodeString = "if (R[" + d + "] > 0) goto " + s + t; break; case 'E': pseudoCodeString = "goto R[" + d + "]"; break; case 'F': if (d == '0') pseudoCodeString = "goto " + s + t; else pseudoCodeString = "R[" + d + "] <- PC; goto " + s + t; break; default: throw new RuntimeException(); } buffer.delete(0, buffer.length()); buffer.append(hexString); buffer.append(" ("); buffer.append(formattedBinaryString); buffer.append(", "); buffer.append(decimalString); buffer.append(')'); detailedString = buffer.toString(); } /** * Constructs an uninitialized TWord. */ private TWord() { this((short)0); isInitialized = false; } /** * checks if the given parameter is a valid TOY instruction */ public static boolean isCommand(String cmd) { if (cmd.length() != 8) return false; for (int i = 0; i < 8; i++) { if ((i < 2 || i > 3) && (!TWord.isHexDigit(cmd.charAt(i)))) return false; else if (i == 2 && cmd.charAt(i) != ':') return false; else if (i == 3 && cmd.charAt(i) != ' ') return false; } return true; } /** takes a line that has an instruction and inserts a comment into the line * preserving what ever user comments there are. If what follows the instruction * is a pseudocode string, this pseudocode is replaced with the new one. */ public static String commentLine(String line) { int lineLength = line.length(); // preserve whatever is after the psuedocode (if there is) int extraStart = 8; int end = TProgramDocument.COMMENT_COLUMN; if (end > lineLength) end = lineLength; String extra = ""; if (TWord.isPseudoCode(line.substring(8, end))) { extraStart = end; } if (lineLength > extraStart) extra = line.substring(extraStart, lineLength); int arrayLength = TProgramDocument.COMMENT_COLUMN - 8; char array[] = new char[arrayLength]; // Generate PseudoCode String pseudoCodeString; // check if this is a constant (memory locations 0 - F) if (line.charAt(0) == '0' && line.charAt(1) >= '0' && line.charAt(1) <= 'F') { pseudoCodeString = "constant 0x" + line.substring(4, 8); } else { TWord command = TWord.parseWord(line.substring(4, 8), 16); pseudoCodeString = command.toPseudoCodeString(false); } int pseudoCodeStringLength = pseudoCodeString.length(); pseudoCodeString.getChars(0, pseudoCodeStringLength, array, 3); array[0] = ' '; array[1] = ' '; array[2] = ' '; // Pad PseudoCode with trailing spaces for (int ctr = pseudoCodeStringLength + 3; ctr < arrayLength; ctr++) { array[ctr] = ' '; } return line.substring(0, 8) + new String(array) + extra; } /** * Checks if the given string is a valid TOY pseudocode */ public static boolean isPseudoCode(String str) { str = str.trim(); boolean match = str.matches("constant 0x[0-9A-F][0-9A-F][0-9A-F][0-9A-F]") || str.matches("R\\[[0-9A-F]] <- R\\[[0-9A-F]] ([+\\-&^]|<<|>>) R\\[[0-9A-F]]") || str.matches("((read)|(write)) R\\[[0-9A-F]]") || str.matches("goto [0-9A-F][0-9A-F]") || str.matches("R\\[[0-9A-F]] <- PC; goto [0-9A-F][0-9A-F]") || str.matches("R\\[[0-9A-F]] <- 00[0-9A-F][0-9A-F]") || str.matches("R\\[[0-9A-F]] <- M\\[[0-9A-F][0-9A-F]]") || str.matches("M\\[[0-9A-F][0-9A-F]] <- R\\[[0-9A-F]]") || str.matches("R\\[[0-9A-F]] <- M\\[R\\[[0-9A-F]]]") || str.matches("M\\[R\\[[0-9A-F]]] <- R\\[[0-9A-F]]") || str.matches("if \\(R\\[[0-9A-F]] ((==)|>) 0\\) goto [0-9A-F][0-9A-F]") || str.matches("goto R\\[[0-9A-F]]") || str.matches("R\\[[0-9A-F]] <- R\\[[0-9A-F]]") || str.matches("R\\[[0-9A-F]] <- -R\\[[0-9A-F]]") || str.equals("halt") || str.equals("no-op"); return match; } /** * Returns wheter or not this word has been defined yet. * * @return True iff this word is not UNINITIALIZED_VALUE. */ public boolean isInitialized() { return isInitialized; } /** * Returns the short primitive to which this TWord corresponds. * * @return The value of this TWord. Note that both getWord(0) and UNINITIALIZED_VALUE have the * same value. */ public short getValue() { return value; } /** * Returns the leftmost/most-significant four bits of the TWord. * * @return (byte)((getValue() >> 12) & 0xF) */ public byte getOp() { return (byte)((value >> 12) & 0xF); } /** * Returns the second leftmost/most-significant four bits of the TWord. * * @return (byte)((getValue() >> 8) & 0xF) */ public byte getD() { return (byte)((value >> 8) & 0xF); } /** * Returns the third leftmost/most-significant four bits of the TWord. * * @return (byte)((getValue() >> 4) & 0xF) */ public byte getS() { return (byte)((value >> 4) & 0xF); } /** * Returns the rightmost/least-significant four bits of the TWord. * * @return (byte)(getValue() & 0xF) */ public byte getT() { return (byte)((value >> 0) & 0xF); } /** * Returns the initialized equivalent of this word. * * @return The initialized equivalent of this word. */ public TWord initializedValue() { if (!isInitialized) return ZERO; else return this; } /** * Returns the rightmost/least-significant eight bits of the TWord. * * @return (short)(getValue() & 0xFF) */ public short getImm() { return (short)((value >> 0) & 0xFF); } /** * Returns a TWord initialize to the given value. * * @param value The value which the TWord should take. * @return The TWord with the given value. Note that getWord(n).isInitialized() should always * evaluate to true for any n. */ public static TWord getWord(short value) { TWord answer = null; Reference reference = CACHED_REFERENCES[value - Short.MIN_VALUE]; if (reference == null) { answer = new TWord(value); CACHED_REFERENCES[value - Short.MIN_VALUE] = new SoftReference(answer); } else { answer = (TWord)reference.get(); if (answer == null) { answer = new TWord(value); CACHED_REFERENCES[value - Short.MIN_VALUE] = new SoftReference(answer); } } return answer; } /** * Parses a TWord from a string. * * @param string The string to parse. * @param radix The base of the number being parsed (probably 2, 10, or 16). * @return getWord(Short.parseShort(string, radix)). */ public static TWord parseWord(String string, int radix) throws NumberFormatException { int n = Integer.parseInt(string, radix); if ((n & 0xFFFF0000) != 0 && (n & 0xFFFF0000) != 0xFFFF0000) throw new NumberFormatException(); return getWord((short)n); } /** * Evaluates wheter or not two TWords have the same values. Due to the instance sharing * properties of TWord, word1.equals(word2) should behave exactly like word1 == word2. * * @param obj The object being compared to this TWord. * @return True iff obj is an instance of TWord with the same initialized state and value as * this TWord. */ public boolean equals(Object obj) { if (obj instanceof TWord) { return ((TWord)obj).isInitialized == isInitialized && ((TWord)obj).value == value; } else return false; } /** * Returns the binary representation of this TWord. Eg. "0101101001101100" * * @param distinguishUninitialized Wheter or not to return a string of question marks if this * TWord is UNINITIALIZED_VALUE. * @return The binary representation of this TWord. Note that the string will be exactly * 16 characters long and also that the leading zeros will be shown. */ public String toBinaryString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return binaryString; else return "????????????????"; } /** * Returns the formatted binary representation of this TWord in which every four bits are * separated by a space. Eg. "0101 1010 0110 1100" * * @param distinguishUninitialized Wheter or not to return a string of question marks if this * TWord is UNINITIALIZED_VALUE. * @return The formatted binary representation of this TWord. Note that the string will be * exactly 19 characters long and also that the leading zeros will * be shown. */ public String toFormattedBinaryString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return formattedBinaryString; else return "???? ???? ???? ????"; } /** * Returns the decimal representation of this TWord. Eg. " 23148" * * @param distinguishUninitialized Wheter or not to return a string of question marks if this * TWord is UNINITIALIZED_VALUE. * @return The decimal representation of this TWord. This string will have exactly 6 * characters; left padding will be done with spaces. */ public String toDecimalString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return decimalString; else return ("??????"); } /** * Returns the hexidecimal representation of this TWord. Eg. "5A6C" * * @param distinguishUninitialized Wheter or not to return a string of question marks if this * TWord is UNINITIALIZED_VALUE. * @return The decimal representation of this TWord. Note that the string will be exactly * 4 characters long and also that the leading zeros will be shown. */ public String toHexString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return hexString; else return "????"; } /** * Returns the detailed representation of this TWord. Eg. * "5A6C (0101 1010 0110 1100,  23148" * * @return toString(false); */ public String toString() { return detailedString; } /** * Returns the detailed representation of this TWord. Eg. * "5A6C (0101 1010 0110 1100,  23148" * * @param distinguishUninitialized Wheter or not to return a string of question marks if this * TWord is UNINITIALIZED_VALUE. * @return The detailed representation of this TWord. Note that the string will be exactly * 33 characters long and also that the leading zeros will be shown. */ public String toString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return detailedString; else return "???? (Uninitialized Value)"; } /** * Returns the pseudo-code form of the TWord. Eg. "R[A] <- R[6] << R[C]" * * @param distinguishUninitialized Wheter or not to return "Uninitialized Value" if * this TWord is UNINITIALIZED_VALUE. * @return The pesudo-code representation of this TWord. Note that the length of the returned * string is variable. */ public String toPseudoCodeString(boolean distinguishUninitialized) { if (isInitialized || !distinguishUninitialized) return pseudoCodeString; else return "Uninitialized Value"; } /** * Implements the addition operator for the TWord type. This could result in either a * TException of type REGISTER_UNINITIALIZED or OVERFLOW. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a + b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#OVERFLOW */ public static TWord add(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { // We add the values as ints so we can check for overflow int sum = a.value + b.value; if ((!a.isInitialized || !b.isInitialized) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if ((sum < Short.MIN_VALUE || sum > Short.MAX_VALUE) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.OVERFLOW); return getWord((short)sum); } /** * Implements the subtraction operator for the TWord type. This could result in either a * TException of type REGISTER_UNINITIALIZED or OVERFLOW. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a - b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#OVERFLOW */ public static TWord subtract(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { if ((!a.isInitialized || !b.isInitialized) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); // We subtract the values as ints so we can check for overflow int difference = a.value - b.value; if ((difference < Short.MIN_VALUE || difference > Short.MAX_VALUE) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.OVERFLOW); return getWord((short)difference); } /** * Implements the bitwise-and operator for the TWord type. This could result in a * TException of type REGISTER_UNINITIALIZED. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a & b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#OVERFLOW */ public static TWord and(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { if ((!a.isInitialized() || !b.isInitialized()) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); return getWord((short)(a.getValue() & b.getValue())); } /** * Implements the bitwise-xor operator for the TWord type. This could result in a * TException of type REGISTER_UNINITIALIZED. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a ^ b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#OVERFLOW */ public static TWord xor(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { if ((!a.isInitialized() || !b.isInitialized()) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); return getWord((short)(a.getValue() ^ b.getValue())); } /** * Implements the logical-left-shift operator for the TWord type. This could result in either a * TException of type REGISTER_UNINITIALIZED or SHIFT_OUT_OF_BOUNDS. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a << b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#SHIFT_OUT_OF_BOUNDS */ public static TWord leftShift(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { if ((!a.isInitialized() || !b.isInitialized()) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if ((b.getValue() & 0xF) != b.getValue() && exceptionHandler != null) exceptionHandler.raise(TExceptionType.SHIFT_OUT_OF_BOUNDS); return getWord((short)(a.getValue() << (b.getValue() & 0xF))); } /** * Implements the arithmatic-right-shift operator for the TWord type. This could result in * either a TException of type REGISTER_UNINITIALIZED or SHIFT_OUT_OF_BOUNDS. * * @param a The left operand. * @param b The right operand. * @param exceptionHandler The hander for any exceptional conditions which might be raised. If * the exceptionHandler is null, all exceptions will be ignored. * @return a >>> b. * @see TExceptionType#REGISTER_UNINITIALIZED * @see TExceptionType#SHIFT_OUT_OF_BOUNDS */ public static TWord rightShift(TWord a, TWord b, TExceptionHandler exceptionHandler) throws TException { if ((!a.isInitialized() || !b.isInitialized()) && exceptionHandler != null) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if ((b.getValue() & 0xF) != b.getValue() && exceptionHandler != null) exceptionHandler.raise(TExceptionType.SHIFT_OUT_OF_BOUNDS); return getWord((short)(a.getValue() >> (b.getValue() & 0xF))); } /** * Returns whether or not a character is a hexidecimal digit. This test is case-insensitive. * * @param c The character to test. * @return True iff the c is a hexidecimal digit. */ public static boolean isHexDigit(char c) { return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f'; } /** * Returns the hexidecimal value of a character. This function is case-insensitive. * * @param c The character to convert. If this character is not a hex digit, an * IllegalArgumentException will be thrown. * @return The value c interpreted as a hexidecimal digit. */ public static int hexDigitToInt(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; throw new IllegalArgumentException(); } }