package edu.princeton.toy; import java.io.*; import edu.princeton.toy.lang.*; import edu.princeton.swing.text.*; /** * TProgramDocument is a class which encapsulates the information associated with a toy program. * It handles the program text parsing and also interfaces with a TVirtualMachine to reset its * memory to contain the program whenever the workspace's reset() function is called. * * @author btsang * @version 7.1 */ public class TProgramDocument extends HighlightedDocument { /** * The maximum number of warnings before a fatal error is thrown. */ public static final int MAX_WARNINGS = 30; /** * The index of the header style. */ public static final byte STYLE_IDENTIFIER = (byte)0; /** * The index of the instruction style. */ public static final byte STYLE_INSTRUCTION = (byte)1; /** * The index of the keyword style. */ public static final byte STYLE_KEYWORD = (byte)2; /** * The index of the comment style. */ public static final byte STYLE_COMMENT = (byte)3; /** * The index of the auto-comment style. */ public static final byte STYLE_AUTO_COMMENT = (byte)4; /** * The index of the outside-margin style. */ public static final byte STYLE_OUTSIDE_MARGIN = (byte)5; /** * The default title a workspace should take if no program line was found. */ public static final String DEFAULT_TITLE = "Untitled"; /** * The bar the separates the description of a program from the code of the program. */ public static final String HEADER_BAR = "// -----------------------------------------------------------------------------"; /** * The column at which normal (non-autocomment) comments begin. */ public static final int COMMENT_COLUMN = 41; /** * The column at which text is considered past the margin. */ public static final int OUTSIDE_MARGIN_COLUMN = 80; private static final TWordBuffer EMPTY_BUFFER = new TWordBuffer(); //////////////////////////////////////////////////////////////////////////// // Variables private static int untitledCtr = 1; private String title; private TWordBuffer initialStdin; private TWord initialMem[]; private int lineDefined[]; /** * Constructs a TProgramDocument with a template program. */ public TProgramDocument() { this( "/******************************************************************************" + "\n * Name:" + "\n * NetID:" + "\n * Precept:" + "\n *" + "\n * Description:" + "\n *" + "\n ******************************************************************************/" ); untitledCtr++; } /** * Constructs a TProgramDocument with the given program. */ public TProgramDocument(String text) { super(); initialStdin = new TWordBuffer(); initialMem = new TWord[TVirtualMachine.MEM_COUNT]; lineDefined = new int[TVirtualMachine.MEM_COUNT]; setText(text); } /** * Returns the line on which the given memory address was defined. * * @param address The address in question. An invalid value will result in an * ArrayIndexOutOfBounds exception. * @param returnLastLine Wheter or not to return the last line if the address was never * defined. * @return The line on which the given memory address was defined. Either the last line or -1 * is returned if the given memory address was never defined (depending on the value of * returnLastLine). */ public int getLineDefined(int address, boolean returnLastLine) { if (address < 0 || address >= TVirtualMachine.MEM_COUNT) throw new ArrayIndexOutOfBoundsException(); int answer; try { readLock(); answer = lineDefined[address]; if (answer == -1 && returnLastLine) answer = lineCount - 1; } finally { readUnlock(); } return answer; } /** * Returns a list of warnings along with character indices of the start and end of the relevant * text. * * @param ignoreWarnings Wheter or not to ignore normal warnings and only report fatal errors. * @return A newly allocated WarningInfoStruct containing a list of warnings along with * character indices of the start and end of the relevant text. */ public WarningInfoStruct getWarnings(boolean ignoreWarnings) { return getWarnings(null, ignoreWarnings); } /** * Returns a list of warnings along with character indices of the start and end of the relevant * text. * * @param warningInfo A WarningInfoStruct which can be reused. If this is null, a new * WarningInfoStruct will be returned. * @param ignoreWarnings Wheter or not to ignore normal warnings and only report fatal errors. * @return A newly allocated (or newly modified) WarningInfoStruct containing a list of * warnings along with character indices of the start and end of the relevant text. */ public WarningInfoStruct getWarnings(WarningInfoStruct warningInfo, boolean ignoreWarnings) { if (warningInfo == null) warningInfo = new WarningInfoStruct(); boolean hasFatalError = false; String warnings[] = warningInfo.warnings; int selectionStart[] = warningInfo.selectionStart; int selectionEnd[] = warningInfo.selectionEnd; int warningCount = 0; try { readLock(); int charCtr, lineCtr; int previousAddress = -1; boolean functionEncountered = false; // Iterate through line by line (because special things can only happen at the start of // a new line). lineCtr = 0; while (!hasFatalError && lineCtr < lineCount && warningCount <= MAX_WARNINGS) { charCtr = lineOffsets[lineCtr]; int lineLength; if (lineCtr == lineCount - 1) lineLength = charCount - charCtr; else lineLength = lineOffsets[lineCtr + 1] - charCtr - 1; if (TWord.isHexDigit(chars[charCtr ]) && charCtr + 7 < charCount && TWord.isHexDigit(chars[charCtr + 1]) && chars[charCtr + 2] == ':' && chars[charCtr + 3] == ' ' && TWord.isHexDigit(chars[charCtr + 4]) && TWord.isHexDigit(chars[charCtr + 5]) && TWord.isHexDigit(chars[charCtr + 6]) && TWord.isHexDigit(chars[charCtr + 7])) { int address = (TWord.hexDigitToInt(chars[charCtr ]) << 4) | TWord.hexDigitToInt(chars[charCtr + 1]); int op = TWord.hexDigitToInt(chars[charCtr + 4]); int d = TWord.hexDigitToInt(chars[charCtr + 5]); int s = TWord.hexDigitToInt(chars[charCtr + 6]); int t = TWord.hexDigitToInt(chars[charCtr + 7]); int imm = (s << 4) | t; short instruction = (short)((op << 12) | (d << 8) | (s << 4) | t); // Fatal errors on which the text can't even be translated // into mem blocks if (previousAddress >= address) { if (initialMem[address].isInitialized()) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Fatal error at " + TWord.HEX_PAIRS[address] + ":\n" + "This address location has already been defined. Please " + "renumber your lines appropriately.\n"; hasFatalError = true; warningCount++; } else { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Fatal error at " + TWord.HEX_PAIRS[address] + ":\n" + "This address location is out of order. Please renumber your " + "lines appropriately.\n"; hasFatalError = true; warningCount++; } } // Don't bother with any more warnings if there's a fatal error or if we've // been ordered to ignore warnings. if (!hasFatalError && !ignoreWarnings && warningCount <= MAX_WARNINGS && address >= 0x10) { // Check to make sure 10 is defined if (!initialMem[0x10].isInitialized() && address > 0x10 && previousAddress < 0x10 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at 10:\n" + "The line 0x10 must be defined if your program is to run at " + "all. Please renumber your lines appropriately.\n"; warningCount++; } // Check to make sure FF isn't being defined if (address == 0xFF && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at FF:\n" + "Memory location FF is reserved for input and output " + "operations. You may not be able to access this data.\n"; warningCount++; } // Check for a missing line if (address > previousAddress + 1 && previousAddress >= 0x10 && !functionEncountered && warningCount <= MAX_WARNINGS) { if (address == previousAddress + 2) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning between " + TWord.HEX_PAIRS[previousAddress] + " and " + TWord.HEX_PAIRS[address] + ":\n" + "There is no definition of line " + TWord.HEX_PAIRS[address - 1] + ". Note that this may not be a fatal error, it may be " + "the space between functions. If this really is the space " + "between functions, please leave a function comment " + "(Tools:: Insert Function Comment).\n"; warningCount++; } else { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning between " + TWord.HEX_PAIRS[previousAddress] + " and " + TWord.HEX_PAIRS[address] + ":\n" + "There are undefined lines. Note that this may not be a " + "fatal error, it may be the space between functions. If " + "this really is the space between functions, please leave a " + "function comment (Tools:: Insert Function Comment).\n"; warningCount++; } } // Check for attempts to assign a value to a constant except // with a 1000 command if (d == 0 && (op >= 1 && op <= 8 || op == 0xA || op == 0xF) && instruction != 0x1000 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "R[0] is a constant. It should not be the destination of any " + "instruction with the exception of nop.\n"; warningCount++; } // Check for non-zero assignments to digits which arn't used if (op == 0 && (d != 0 || s != 0 || t != 0) && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "Operator 0 (halt) does not require a 'd', 's', or 't'. It is " + "conventional to halt a program with \"0000\".\n"; warningCount++; } if (op == 0xA && s != 0 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "Operator A (load indirect) does not require an 's'. It is " + "conventional to assign the 3rd digit to '0'.\n"; warningCount++; } if (op == 0xB && s != 0 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "Operator B (store indirect) does not require an 's'. It is " + "conventional to assign the 3rd digit to '0'.\n"; warningCount++; } if (op == 0xE && (s != 0 || t != 0) && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "Operator E (jump register) does not require an 's' or 't'. It " + "is conventional to terminate such a command with two '0's.\n"; warningCount++; } // Check for simple infinite loops if (op == 0xF && imm == address && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "This command is an infinite loop.\n"; warningCount++; } if ((op == 0xC || op == 0xD) && imm == address && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "This command could be an infinite loop.\n"; warningCount++; } // Check for lines which are guaranteed not to do a thing if ((op == 0xC || op == 0xD) && imm == address + 1 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "This is a command to jump to the next line. In other words, " + "this line appears to serve no purpose.\n"; warningCount++; } if (op == 0xD && d == 0 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "Operator D (branch positive) will not branch since R[0] is " + "always 0. In other words, his line appears to serve no " + "purpose.\n"; warningCount++; } if ((op == 1 || op == 3) && (d == s && t == 0 || d == t && s == 0) && instruction != 0x1000 && warningCount <= MAX_WARNINGS || op == 2 && d == s && t == 0 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "This command will not change R[" + TWord.HEX_DIGITS[d] + "] because R[0] is always 0. In other words, this line appears " + "to serve no purpose.\n"; warningCount++; } if (op == 4 && d == s && t == 0 && warningCount <= MAX_WARNINGS || (op == 5 || op == 6) && d == s && t == 0 && warningCount <= MAX_WARNINGS) { selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = charCtr + 8; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ":\n" + "This command will not change R[" + TWord.HEX_DIGITS[d] + "]. In other words, this line appears to serve no purpose.\n"; warningCount++; } } previousAddress = address; functionEncountered = false; } else if ((chars[charCtr ] == 'f' || chars[charCtr ] == 'F') && (charCtr + 7 < charCount) && (chars[charCtr + 1] == 'u' || chars[charCtr + 1] == 'U') && (chars[charCtr + 2] == 'n' || chars[charCtr + 2] == 'N') && (chars[charCtr + 3] == 'c' || chars[charCtr + 3] == 'C') && (chars[charCtr + 4] == 't' || chars[charCtr + 4] == 'T') && (chars[charCtr + 5] == 'i' || chars[charCtr + 5] == 'I') && (chars[charCtr + 6] == 'o' || chars[charCtr + 6] == 'O') && (chars[charCtr + 7] == 'n' || chars[charCtr + 7] == 'N') && (charCtr + 8 == charCount || chars[charCtr + 8] == ' ' || chars[charCtr + 8] == '\n')) { functionEncountered = true; } else if (TWord.isHexDigit(chars[charCtr])) { // Due to the regexp qualities of scanf, the C parser is alot looser as to what // qualifies as a valid command. Here is the essential regexp: // instruction <= (X)(X?)(':')(S*)((X)|(XX)|(XXX)|(XXXX)) // Where X is a macro for a hex digit and S is a macro for any whitespace character // We have to warn the user if the program will be parsed differently in the C // parser int address = TWord.hexDigitToInt(chars[charCtr]); int peekCtr = charCtr + 1; boolean isProblem = true; if (isProblem && peekCtr < charCount) { if (chars[peekCtr] == ':') { peekCtr++; } else if (peekCtr + 1 < charCount && TWord.isHexDigit(chars[peekCtr]) && chars[peekCtr + 1] == ':') { address = (address << 4) | TWord.hexDigitToInt(chars[peekCtr]); peekCtr += 2; } else { isProblem = false; } } if (isProblem && peekCtr < charCount) { while (peekCtr < charCount && (chars[peekCtr] == ' ' || chars[peekCtr] == '\n')) peekCtr++; if (peekCtr < charCount && TWord.isHexDigit(chars[peekCtr])) { int instruction = TWord.hexDigitToInt(chars[peekCtr]); peekCtr++; if (peekCtr < charCount && TWord.isHexDigit(chars[peekCtr])) { instruction = (instruction << 4) | TWord.hexDigitToInt(chars[peekCtr]); peekCtr++; } if (peekCtr < charCount && TWord.isHexDigit(chars[peekCtr])) { instruction = (instruction << 4) | TWord.hexDigitToInt(chars[peekCtr]); peekCtr++; } if (peekCtr < charCount && TWord.isHexDigit(chars[peekCtr])) { instruction = (instruction << 4) | TWord.hexDigitToInt(chars[peekCtr]); peekCtr++; } selectionStart[warningCount] = charCtr; selectionEnd[warningCount] = peekCtr; warnings[warningCount] = "Warning at " + TWord.HEX_PAIRS[address] + ": " + "Did you mean \"" + TWord.HEX_PAIRS[address] + ": " + TWord.getWord((short)instruction).toHexString(false) + "\" instead of \"" + TWord.HEX_PAIRS[address] + ":" + TWord.getWord((short)instruction).toHexString(false) + "\"?"; hasFatalError = false; warningCount++; } } } // Advance to and past the next newline lineCtr++; } // Check to make sure 10 is defined if (previousAddress < 0x10 && !ignoreWarnings && warningCount < MAX_WARNINGS) { selectionStart[warningCount] = charCount; selectionEnd[warningCount] = charCount; warnings[warningCount] = "Warning at 10:\n" + "The line 0x10 must be defined if your program is to run at all. " + "Please renumber your lines appropriately.\n"; warningCount++; } if (!hasFatalError && warningCount == MAX_WARNINGS + 1) { selectionStart[MAX_WARNINGS] = 0; selectionEnd[MAX_WARNINGS] = 0; warnings[MAX_WARNINGS] = "Fatal error:\n" + "Your program has more than " + MAX_WARNINGS + " warnings. Please resolve " + "these warnings before proceeding. If you wish to proceed anyway, turn on " + "the \"Ignore Warnings\" option.\n"; hasFatalError = true; } } finally { readUnlock(); } warningInfo.hasFatalError = hasFatalError; warningInfo.warningCount = warningCount; return warningInfo; } /** * Returns the initial values which should be in stdin whenever the workspace is reset. A * clone of the internal buffer is returned. * * @return A TWordBuffer containing the initial values which should be in stdin whenever the * workspace is reset. */ public TWordBuffer getInitialStdin() { return (TWordBuffer)initialStdin.clone(); } /** * Returns the initial values which should be in stdin whenever the workspace is reset. The * internal buffer's data is copied into the provided buffer. * * @param buffer The buffer to copy the data onto. The buffer will be cleared before the copy. * @return The same buffer which was passed in. */ public TWordBuffer getInitialStdin(TWordBuffer buffer) { buffer.clear(); buffer.add(initialStdin); return buffer; } /** * Sets the initial values which should be in stdin whenever the workspace is reset. * * @param initialStdin The new values which should be in stdin whenever the workspace is * reset. */ public void setInitialStdin(TWordBuffer initialStdin) { if (initialStdin == null) throw new NullPointerException(); this.initialStdin.clear(); this.initialStdin.add(initialStdin); } /** * Returns the title of the workspace. This is derived from the word(s) following * "Program " in the first line of the program. * * @return The title of the workspace. */ public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } /** * Resets the virtual machine then copies the program and initial stdin back in. */ public void reset(TVirtualMachine virtualMachine) { virtualMachine.reset(); virtualMachine.initMem(initialMem); virtualMachine.setStdin(EMPTY_BUFFER, initialStdin); } /** * Returns the number of distinct styles used by this type of document. This must always * return the same number. * * @return The number of distinct styles used by this type of document. */ public int getStyleCount() { return 6; } /** * Returns the number of spaces to replace all tabs with. * * @return The number of spaces to replace all tabs with. */ public int getTabSize() { return 4; } /** * Parses toy code and uses it to highlight the syntax, update the title, and and update the * warning list. Calls to getWarnings(), getProgramSummary(), and reset() should be made as * necessary to make sure that the changes propagate to the warning list, program summary, and * the virtual machine. */ public void assignStyles() { int charCtr, lineCtr; int previousAddress = -1; boolean functionEncountered = false; String title = null; // Clear the initialMem words for (int ctr = 0; ctr < TVirtualMachine.MEM_COUNT; ctr++) { initialMem[ctr] = TWord.UNINITIALIZED_VALUE; lineDefined[ctr] = -1; } // Clear out the old styles for (int ctr = 0; ctr < charCount; ctr++) charStyles[ctr] = STYLE_COMMENT; // Iterate through line by line (because special things can only happen at the start of // a new line). lineCtr = 0; while (lineCtr < lineCount) { charCtr = lineOffsets[lineCtr]; int lineLength; if (lineCtr == lineCount - 1) lineLength = charCount - charCtr; else lineLength = lineOffsets[lineCtr + 1] - charCtr - 1; if (TWord.isHexDigit(chars[charCtr ]) && charCtr + 7 < charCount && TWord.isHexDigit(chars[charCtr + 1]) && chars[charCtr + 2] == ':' && chars[charCtr + 3] == ' ' && TWord.isHexDigit(chars[charCtr + 4]) && TWord.isHexDigit(chars[charCtr + 5]) && TWord.isHexDigit(chars[charCtr + 6]) && TWord.isHexDigit(chars[charCtr + 7])) { int address = (TWord.hexDigitToInt(chars[charCtr ]) << 4) | TWord.hexDigitToInt(chars[charCtr + 1]); int op = TWord.hexDigitToInt(chars[charCtr + 4]); int d = TWord.hexDigitToInt(chars[charCtr + 5]); int s = TWord.hexDigitToInt(chars[charCtr + 6]); int t = TWord.hexDigitToInt(chars[charCtr + 7]); int imm = (s << 4) | t; short instruction = (short)((op << 12) | (d << 8) | (s << 4) | t); initialMem[address] = TWord.getWord(instruction); lineDefined[address] = lineCtr; previousAddress = address; // Highlight the syntax for (int ctr = 0; ctr < 8; ctr++) charStyles[charCtr + ctr] = STYLE_INSTRUCTION; if (lineLength <= COMMENT_COLUMN) { for (int ctr = 8; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_AUTO_COMMENT; } else { for (int ctr = 8; ctr < COMMENT_COLUMN; ctr++) charStyles[charCtr + ctr] = STYLE_AUTO_COMMENT; if (lineLength <= OUTSIDE_MARGIN_COLUMN) { for (int ctr = COMMENT_COLUMN; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_COMMENT; } else { for (int ctr = COMMENT_COLUMN; ctr < OUTSIDE_MARGIN_COLUMN; ctr++) charStyles[charCtr + ctr] = STYLE_COMMENT; for (int ctr = OUTSIDE_MARGIN_COLUMN; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_OUTSIDE_MARGIN; } } } else if ((chars[charCtr ] == 'f' || chars[charCtr ] == 'F') && (charCtr + 7 < charCount) && (chars[charCtr + 1] == 'u' || chars[charCtr + 1] == 'U') && (chars[charCtr + 2] == 'n' || chars[charCtr + 2] == 'N') && (chars[charCtr + 3] == 'c' || chars[charCtr + 3] == 'C') && (chars[charCtr + 4] == 't' || chars[charCtr + 4] == 'T') && (chars[charCtr + 5] == 'i' || chars[charCtr + 5] == 'I') && (chars[charCtr + 6] == 'o' || chars[charCtr + 6] == 'O') && (chars[charCtr + 7] == 'n' || chars[charCtr + 7] == 'N') && (charCtr + 8 == charCount || chars[charCtr + 8] == ' ' || chars[charCtr + 8] == '\n')) { // Highlight the syntax for (int ctr = 0; ctr < 8; ctr++) charStyles[charCtr + ctr] = STYLE_KEYWORD; if (lineLength <= OUTSIDE_MARGIN_COLUMN) { for (int ctr = 8; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_IDENTIFIER; } else { for (int ctr = 8; ctr < OUTSIDE_MARGIN_COLUMN; ctr++) charStyles[charCtr + ctr] = STYLE_IDENTIFIER; for (int ctr = OUTSIDE_MARGIN_COLUMN; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_OUTSIDE_MARGIN; } } else if ((chars[charCtr ] == 'p' || chars[charCtr ] == 'P') && (charCtr + 6 < charCount) && (chars[charCtr + 1] == 'r' || chars[charCtr + 1] == 'R') && (chars[charCtr + 2] == 'o' || chars[charCtr + 2] == 'O') && (chars[charCtr + 3] == 'g' || chars[charCtr + 3] == 'G') && (chars[charCtr + 4] == 'r' || chars[charCtr + 4] == 'R') && (chars[charCtr + 5] == 'a' || chars[charCtr + 5] == 'A') && (chars[charCtr + 6] == 'm' || chars[charCtr + 6] == 'M') && (charCtr + 7 == charCount || chars[charCtr + 7] == ' ' || chars[charCtr + 7] == '\n')) { if (lineLength - 8 > 0) title = (new String(chars, charCtr + 8, lineLength - 8)).trim(); // Highlight the syntax for (int ctr = 0; ctr < 7; ctr++) charStyles[charCtr + ctr] = STYLE_KEYWORD; if (lineLength <= OUTSIDE_MARGIN_COLUMN) { for (int ctr = 7; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_IDENTIFIER; } else { for (int ctr = 7; ctr < OUTSIDE_MARGIN_COLUMN; ctr++) charStyles[charCtr + ctr] = STYLE_IDENTIFIER; for (int ctr = OUTSIDE_MARGIN_COLUMN; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_OUTSIDE_MARGIN; } } else { // Highlight the syntax if (lineLength <= OUTSIDE_MARGIN_COLUMN) { for (int ctr = 0; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_COMMENT; } else { for (int ctr = 0; ctr < OUTSIDE_MARGIN_COLUMN; ctr++) charStyles[charCtr + ctr] = STYLE_COMMENT; for (int ctr = OUTSIDE_MARGIN_COLUMN; ctr < lineLength; ctr++) charStyles[charCtr + ctr] = STYLE_OUTSIDE_MARGIN; } } // Advance to and past the next newline lineCtr++; } if (title == null) this.title = DEFAULT_TITLE; else this.title = title; } /** * Updates pseudocode comments and performs minor stylistic changes (standardizing * capitalization and putting '//'s before all comments). */ public void autocomment() { int newPositions[] = null; StringBuffer buffer = null; try { readLock(); newPositions = getPositionOffsets(); int positionCount = newPositions.length; int oldPositions[] = new int[positionCount]; for (int ctr = 0; ctr < positionCount; ctr++) oldPositions[ctr] = newPositions[ctr]; buffer = new StringBuffer(2 * charCount); StringBuffer buffer2 = new StringBuffer(2 * charCount); boolean programEncountered = false; int charCtr = 0; while (charCtr < charCount) { if (TWord.isCommand(new String(chars, charCtr, 8))) { int address = (TWord.hexDigitToInt(chars[charCtr ]) << 4) | TWord.hexDigitToInt(chars[charCtr + 1]); TWord command = TWord.getWord( (short)( (TWord.hexDigitToInt(chars[charCtr + 4]) << 12) | (TWord.hexDigitToInt(chars[charCtr + 5]) << 8) | (TWord.hexDigitToInt(chars[charCtr + 6]) << 4) | TWord.hexDigitToInt(chars[charCtr + 7]) ) ); // Output the command, formatted our way buffer.append(Character.toUpperCase(chars[charCtr ])); buffer.append(Character.toUpperCase(chars[charCtr + 1])); buffer.append(": "); buffer.append(Character.toUpperCase(chars[charCtr + 4])); buffer.append(Character.toUpperCase(chars[charCtr + 5])); buffer.append(Character.toUpperCase(chars[charCtr + 6])); buffer.append(Character.toUpperCase(chars[charCtr + 7])); // In order for us to safely update a comment, it needs to satisfy one of // the following two conditions: // 1 All characters between the instruction and the next newline are // spaces. // 2 All there is a string of COMMENT_COLUMN - 8 characters following the // the instruction which begins with 3 spaces and ends with at least one // space. (Obviously, no newlines may be in between). boolean updateComment = false; int peekCtr = charCtr + 8; // Check for condition 1 while (peekCtr < charCount && chars[peekCtr] == ' ') peekCtr++; if (peekCtr == charCount) { // Since we're about to do a string replacement, we need to correct our // positions. if (peekCtr - charCtr > COMMENT_COLUMN) { // We're contracting the string of spaces by replacing it with a // comment for (int ctr = 0; ctr < positionCount; ctr++) { int oldPosition = oldPositions[ctr]; if (oldPosition > charCtr + COMMENT_COLUMN) { newPositions[ctr] -= oldPosition - charCtr - COMMENT_COLUMN; } } } else { // We're expanding the string of spaces by replacing it with a comment for (int ctr = 0; ctr < positionCount; ctr++) { int oldPosition = oldPositions[ctr]; if (oldPosition == peekCtr) { newPositions[ctr] += charCtr + COMMENT_COLUMN - oldPosition; } } } charCtr = peekCtr; updateComment = true; } else if (chars[peekCtr] == '\n') { // Since we're about to do a string replacement, we need to correct our // positions. if (peekCtr - charCtr > COMMENT_COLUMN) { // We're contracting the string of spaces by replacing it with a // comment for (int ctr = 0; ctr < positionCount; ctr++) { int oldPosition = oldPositions[ctr]; if (oldPosition > charCtr + COMMENT_COLUMN) { if (oldPosition <= peekCtr) newPositions[ctr] -= oldPosition - charCtr - COMMENT_COLUMN; else newPositions[ctr] -= peekCtr - charCtr - COMMENT_COLUMN; } } } else { // We're expanding the string of spaces by replacing it with a comment for (int ctr = 0; ctr < positionCount; ctr++) { if (oldPositions[ctr] >= peekCtr) newPositions[ctr] += charCtr + COMMENT_COLUMN - peekCtr; } } charCtr = peekCtr; updateComment = true; } // Check for condition 2 if (!updateComment && charCtr + COMMENT_COLUMN < charCount) { peekCtr = charCtr + 8; while (peekCtr < charCtr + COMMENT_COLUMN && chars[peekCtr] != '\n') peekCtr++; if (peekCtr == charCtr + COMMENT_COLUMN && chars[charCtr + 8] == ' ' && chars[charCtr + 9] == ' ' && chars[charCtr + 10] == ' ' && chars[charCtr + COMMENT_COLUMN - 1] == ' ') { // Here, we are replacing a string with one of equal length, so no // position update has to be done updateComment = true; charCtr = peekCtr; } } // Update the comment based wheter or not one of the conditions was met if (updateComment) { // We can update the comment if (address < TVirtualMachine.PC_START.getValue()) { buffer2.delete(0, buffer2.length()); buffer2.append(" ("); buffer2.append(command.toFormattedBinaryString(false)); buffer2.append(", "); buffer2.append(command.toDecimalString(false)); buffer2.append(") "); buffer.append(buffer2); } else { buffer2.delete(0, buffer2.length()); buffer2.append(" "); buffer2.append(command.toPseudoCodeString(false)); while (buffer2.length() < COMMENT_COLUMN - 8) buffer2.append(' '); buffer.append(buffer2); } } else { // We can't update the comment charCtr += 8; } } else if ((chars[charCtr ] == 'f' || chars[charCtr ] == 'F') && (charCtr + 7 < charCount) && (chars[charCtr + 1] == 'u' || chars[charCtr + 1] == 'U') && (chars[charCtr + 2] == 'n' || chars[charCtr + 2] == 'N') && (chars[charCtr + 3] == 'c' || chars[charCtr + 3] == 'C') && (chars[charCtr + 4] == 't' || chars[charCtr + 4] == 'T') && (chars[charCtr + 5] == 'i' || chars[charCtr + 5] == 'I') && (chars[charCtr + 6] == 'o' || chars[charCtr + 6] == 'O') && (chars[charCtr + 7] == 'n' || chars[charCtr + 7] == 'N') && (charCtr + 8 == charCount || chars[charCtr + 8] == ' ' || chars[charCtr + 8] == '\n')) { // Force all function keywords to be lowercase (we're just replacing a string // with another of equal length, so we don't need to update the positions) buffer.append("function"); charCtr += 8; } else if ((chars[charCtr ] == 'p' || chars[charCtr ] == 'P') && (charCtr + 6 < charCount) && (chars[charCtr + 1] == 'r' || chars[charCtr + 1] == 'R') && (chars[charCtr + 2] == 'o' || chars[charCtr + 2] == 'O') && (chars[charCtr + 3] == 'g' || chars[charCtr + 3] == 'G') && (chars[charCtr + 4] == 'r' || chars[charCtr + 4] == 'R') && (chars[charCtr + 5] == 'a' || chars[charCtr + 5] == 'A') && (chars[charCtr + 6] == 'm' || chars[charCtr + 6] == 'M') && (charCtr + 7 == charCount || chars[charCtr + 7] == ' ' || chars[charCtr + 7] == '\n')) { // Put the first program line at the top, comment out all other program lines if (buffer.length() == 0) { // If the first program line is at the top, make sure the word "program" // is lowercase (again, we're just replacing a string with another of equal // length, so we don't need to update the positions) buffer.append("program"); charCtr += 7; } else { // If the first program line is not at the top, put a copy at the top if (!programEncountered) { buffer2.delete(0, buffer2.length()); buffer2.append("program"); int peekCtr = charCtr + 7; while (peekCtr < charCount && chars[peekCtr] != '\n') { buffer.append(chars[peekCtr]); peekCtr++; } buffer2.append('\n'); buffer.insert(0, buffer2); // All positions except the start get shifted up by buffer2's length int insertedStringLength = buffer2.length(); for (int ctr = 0; ctr < positionCount; ctr++) { if (oldPositions[ctr] != 0) newPositions[ctr] += insertedStringLength; } } } programEncountered = true; } else { int peekCtr = charCtr; while (peekCtr < charCount && chars[peekCtr] == ' ') peekCtr++; } // Advance to the newline while (charCtr < charCount && chars[charCtr] != '\n') { buffer.append(chars[charCtr]); charCtr++; } // Advance past the newline if (charCtr < charCount) { buffer.append('\n'); charCtr++; } } } finally { readUnlock(); } replace(0, Integer.MAX_VALUE, buffer.toString(), false, true, newPositions, true); } /** * Strips all comments (except header comments) off the program. */ public void stripComments() { StringBuffer buffer; try { readLock(); buffer = new StringBuffer(charCount); // Look for the HEADER_BAR int headerBarIndex = -1; int headerBarLength = HEADER_BAR.length(); for (int lineCtr = 0; lineCtr < lineCount && headerBarIndex == -1; lineCtr++) { int charCtr = lineOffsets[lineCtr]; int lineLength; if (lineCtr == lineCount - 1) lineLength = charCount - charCtr; else lineLength = lineOffsets[lineCtr + 1] - charCtr - 1; if (lineLength == headerBarLength) { boolean match = true; for (int ctr = 0; ctr < headerBarLength && match; ctr++) { if (chars[charCtr + ctr] != HEADER_BAR.charAt(ctr)) match = false; } if (match) headerBarIndex = charCtr; } } // Put the header bar, and everything above it in the new text if (headerBarIndex == -1) { buffer.append( "program " + title + "\n" + "// Input: \n" + "// Output: \n" + "// Remarks: \n" + TProgramDocument.HEADER_BAR + "\n" + "\n" ); } else { buffer.append(chars, 0, headerBarIndex); buffer.append(TProgramDocument.HEADER_BAR + "\n\n"); } // Put the stripped program in boolean printedLastLine = true; for (int ctr = 0; ctr < TVirtualMachine.MEM_COUNT; ctr++) { if (initialMem[ctr].isInitialized()) { if (!printedLastLine) buffer.append('\n'); buffer.append(TWord.HEX_PAIRS[ctr]); buffer.append(": "); buffer.append(initialMem[ctr].toHexString(false)); buffer.append('\n'); printedLastLine = true; } else { printedLastLine = false; } } } finally { readUnlock(); } replace(0, Integer.MAX_VALUE, buffer.toString(), false, true, new int[0], true); } /** * WarningInfoStruct is a simple class which encapsulates all of the info that getWarnings() * has to return. * * @author btsang * @version 7.1 */ public static class WarningInfoStruct { public boolean hasFatalError; public int warningCount; public final String warnings[] = new String[MAX_WARNINGS + 1]; public final int selectionStart[] = new int[MAX_WARNINGS + 1]; public final int selectionEnd[] = new int[MAX_WARNINGS + 1]; /** * Creates a new WarningInfoStruct. */ public WarningInfoStruct() { } /** * Returns wheter or not this WarningInfoStruct conveys the same information as another. * * @param object The other object to compare this WarningInfoStruct to. * @return True iff the other object is a non-null instance of WarningInfoStruct which * contains the same information as this WarningInfoStruct. */ public boolean equals(Object object) { if (object == null || !(object instanceof WarningInfoStruct)) return false; if (object == this) return true; WarningInfoStruct warningInfo = (WarningInfoStruct)object; if (warningInfo.hasFatalError != hasFatalError || warningInfo.warningCount != warningCount) { return false; } for (int ctr = 0; ctr < warningCount; ctr++) { if (!warningInfo.warnings[ctr].equals(warnings[ctr]) || warningInfo.selectionStart[ctr] != selectionStart[ctr] || warningInfo.selectionEnd[ctr] != selectionEnd[ctr]) { return false; } } return true; } /** * Returns wheter or not this WarningInfoStruct conveys the same information as another * (except possibly warnings). * * @param warningInfo The other WarningInfoStruct to compare this WarningInfoStruct to. * @return True iff the other object is a non-null instance of WarningInfoStruct which * contains the same information (except possibly warnings) as this WarningInfoStruct. */ public boolean selectionsEqual(WarningInfoStruct warningInfo) { if (warningInfo == null) return false; if (warningInfo.hasFatalError != hasFatalError || warningInfo.warningCount != warningCount) { return false; } for (int ctr = 0; ctr < warningCount; ctr++) { if (warningInfo.selectionStart[ctr] != selectionStart[ctr] || warningInfo.selectionEnd[ctr] != selectionEnd[ctr]) { return false; } } return true; } /** * Returns wheter or not this WarningInfoStruct conveys the same information as another * (except possibly selectionStart and selectionEnd). * * @param warningInfo The other WarningInfoStruct to compare this WarningInfoStruct to. * @return True iff the other object is a non-null instance of WarningInfoStruct which * contains the same information (except possibly selectionStart and selectionEnd) as * this WarningInfoStruct. */ public boolean warningsEqual(WarningInfoStruct warningInfo) { if (warningInfo == null) return false; if (warningInfo.hasFatalError != hasFatalError || warningInfo.warningCount != warningCount) { return false; } for (int ctr = 0; ctr < warningCount; ctr++) { if (!warningInfo.warnings[ctr].equals(warnings[ctr])) { return false; } } return true; } } }