package toy; import toy.dialog.*; /** * * @author Brian Tsang * @version 7.0 */ public class ToyWorkspace { //////////////////////////////////////////////////////////////////////////// // Constants public static final String HEADER_BAR = "--------------------------------------------------------------------------------"; public static final int COMMENT_WIDTH = 33; public static final int UNDO_MEMORY = 60; //////////////////////////////////////////////////////////////////////////// // Variables private String text; private String savedText; private ToyTextAreaState undoData[]; private int undoCtr; private String name; private String fileName; private boolean hasFatalError; private ToyVirtualMachine parsedVirtualMachine; //this virtual machine comes straight from the text this is compared //to initialVirtualMachine to see if any changes were made with the //load button private ToyVirtualMachine initialVirtualMachine; //this virtual machine is the parsedVirtualMachine + changes made with //the load button (this is the machine that virtualMachine is reset to private ToyVirtualMachine virtualMachine; //this is the virtual machine that is stepped through //////////////////////////////////////////////////////////////////////////// // The Constructor public ToyWorkspace(String parseString) { name = "Untitled"; fileName = ""; savedText = parseString; undoData = new ToyTextAreaState[UNDO_MEMORY]; undoCtr = -1; parsedVirtualMachine = new ToyVirtualMachine(); initialVirtualMachine = new ToyVirtualMachine(); virtualMachine = new ToyVirtualMachine(); setText(parseString, false); } public ToyWorkspace(String parseString, String newFileName) { this(parseString, newFileName, new ToyWord[0]); } public ToyWorkspace(String parseString, String newFileName, ToyWord newStdin[]) { try { //Process the filename so we can get a clean name for the //workspace name name = newFileName; int directoryChop = Math.max( name.lastIndexOf('\\'), name.lastIndexOf('/') ); if (directoryChop >= 0) name = name.substring(directoryChop + 1); //drop the extention (".toy" or whatever) if (name.lastIndexOf('.') > name.length() - 6) name = name.substring(0, name.lastIndexOf('.')); //replace underscores with spaces name = name.replace('_', ' '); //uppercase the first character name = Character.toUpperCase(name.charAt(0)) + name.substring(1); //uppercase any character after that which was preceded by //a character which was not a letter for (int ctr = 1; ctr < name.length(); ctr++) if (!Character.isLetter(name.charAt(ctr - 1))) name = name.substring(0, ctr) + Character.toUpperCase(name.charAt(ctr)) + name.substring(ctr + 1); } catch (Exception e) { name = "Untitled"; } fileName = newFileName; savedText = parseString; undoData = new ToyTextAreaState[UNDO_MEMORY]; undoCtr = -1; parsedVirtualMachine = new ToyVirtualMachine(); initialVirtualMachine = new ToyVirtualMachine(); initialVirtualMachine.setStdin(newStdin); virtualMachine = new ToyVirtualMachine(); setText(parseString, false); } //////////////////////////////////////////////////////////////////////////// // the get and set functions public ToyVirtualMachine getVirtualMachine() { return virtualMachine; } public ToyVirtualMachine getInitialVirtualMachine() { return initialVirtualMachine; } public boolean memChanged() { boolean answer = false; for (int ctr = 0; !answer && ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++) answer = !initialVirtualMachine.getMem(ctr).equals( parsedVirtualMachine.getMem(ctr) ); return answer; } public boolean hasFatalError() { return hasFatalError; } public String getText() { return text; } //the setText function is really big so it has its own section public void addUndoData(ToyTextAreaState newUndoData) { if (undoCtr == -1) { undoCtr = 0; undoData[0] = newUndoData; } else { if (!undoData[undoCtr].equals(newUndoData)) { undoCtr++; if (undoCtr >= UNDO_MEMORY) { for (int ctr = 1; ctr < UNDO_MEMORY; ctr++) undoData[ctr - 1] = undoData[ctr]; undoCtr--; } undoData[undoCtr] = newUndoData; //if something was done we can't redo anything from a different //timeline for (int ctr = undoCtr + 1; ctr < UNDO_MEMORY; ctr++) undoData[ctr] = null; } else undoData[undoCtr] = newUndoData; } } public boolean canUndo() { return undoCtr > 0 && undoData[undoCtr - 1] != null; } public ToyTextAreaState undo() { if (canUndo()) undoCtr--; return undoData[undoCtr]; } public boolean canRedo() { return undoCtr != -1 && undoCtr + 1 < UNDO_MEMORY && undoData[undoCtr + 1] != null; } public ToyTextAreaState redo() { if (canRedo()) undoCtr++; return undoData[undoCtr]; } public String getName() { return name; } public void setName(String newName) { name = newName; } public String getFileName() { return fileName; } public void setFileName(String newFileName) { fileName = newFileName; } //////////////////////////////////////////////////////////////////////////// // workSaved() and isSaved() are two functions that help the ToyFrame decide // wheter or not the current text of t program has been saved or not public void workSaved() { savedText = text; } public boolean isSaved() { return savedText.equals(text); } public void revertToSaved() { setText(savedText, false); } //////////////////////////////////////////////////////////////////////////// // reset() resets the virtualMachine and reinitializes the memory with the // text from the most recently parsed string public void reset() { ToyWord stdin[] = initialVirtualMachine.getStdin(); ToyWord stdinCopy[] = new ToyWord[stdin.length]; for (int ctr = 0; ctr < stdin.length; ctr++) stdinCopy[ctr] = stdin[ctr]; virtualMachine.reset(); for (int ctr = 0; ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++) virtualMachine.setMem(ctr, initialVirtualMachine.getMem(ctr)); virtualMachine.setStdin(stdinCopy); } //////////////////////////////////////////////////////////////////////////// // setText() parses a string into the virtualMachine's code and returns // a ToyWarningHash object so the Frame can display the problems with the code public ToyWarningHash setText(String parseString, boolean warningHashEnabled) { int line, charCtr; short address, previousAddress = -1; short op, rd, rs, rt; boolean functionEncountered = false; ToyWarningHash warningHash = new ToyWarningHash(); text = parseString; //change the name if a new one can be parsed //check for changes to the program name if (text.startsWith("Program ")) { int endIndex = text.indexOf('\n'); if (endIndex < 0) endIndex = text.length(); name = text.substring("Program ".length(), endIndex).trim(); } //reset the parsedVirtualMachine parsedVirtualMachine.reset(); //parse the text to rebuild both mem and warningHashs charCtr = 0; line = 1; //advance to first non-whitespace character or newline while (charCtr < text.length() && Character.isWhitespace(text.charAt(charCtr)) && text.charAt(charCtr) != '\n') charCtr++; while (charCtr + 7 < text.length() && !warningHash.maxedOut()) { if (ToyWord.isHexDigit(text.charAt(charCtr)) && ToyWord.isHexDigit(text.charAt(charCtr + 1)) && text.charAt(charCtr + 2) == ':' && text.charAt(charCtr + 3) == ' ' && ToyWord.isHexDigit(text.charAt(charCtr + 4)) && ToyWord.isHexDigit(text.charAt(charCtr + 5)) && ToyWord.isHexDigit(text.charAt(charCtr + 6)) && ToyWord.isHexDigit(text.charAt(charCtr + 7))) { address = (short)(ToyWord.convertFromHexDigit(text.charAt(charCtr)) * 0x10 + ToyWord.convertFromHexDigit(text.charAt(charCtr + 1))); op = ToyWord.convertFromHexDigit(text.charAt(charCtr + 4)); rd = ToyWord.convertFromHexDigit(text.charAt(charCtr + 5)); rs = ToyWord.convertFromHexDigit(text.charAt(charCtr + 6)); rt = ToyWord.convertFromHexDigit(text.charAt(charCtr + 7)); //Fatal errors on which the text can't even be translated //into mem blocks if (previousAddress >= address) { if (parsedVirtualMachine.getMem(address).isDefined()) { warningHash.add( line, "Fatal error at " + ToyWord.toHexString(address).substring(2)+ ":", "This address location has already been defined. Please renumber " + "your lines appropriately." ); hasFatalError = true; return warningHash; } else { warningHash.add( line, "Fatal error at " + ToyWord.toHexString(address).substring(2)+ ":", "This address location is out of order. Please renumber your " + "lines appropriately." ); hasFatalError = true; return warningHash; } } //Just warningHashs, disable if user doesn't want it //(Note how commands in the lower area of memory are not subject // to warningHashs because it's conventionally used for long-term // variable storage) if (warningHashEnabled && address >= 0x10) { //check to make sure 10 is defined if (!parsedVirtualMachine.getMem(0x10).isDefined() && address > 0x10 && previousAddress < 0x10) warningHash.add( line, "Warning at 10:", "The line 0x10 must be defined if your program is to run at all. " + "Please renumber your lines appropriately." ); //check to make sure FF isn't being defined if (address == 0xFF) warningHash.add( line, "Warning at FF:", "Memory location FF is reserved for input and output operations. " + "You may not be able to access this data." ); //check for a missing line if (address == previousAddress + 2 && previousAddress >= 0x10 && !functionEncountered) warningHash.add( line, "Warning between " + ToyWord.toHexString(previousAddress).substring(2) + " and " + ToyWord.toHexString(address).substring(2) + ":", "There is no definition of line " + ToyWord.toHexString((short)(address - 1)).substring(2) + ". 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)." ); //check for missing lines if (address > previousAddress + 2 && previousAddress >= 0x10 && !functionEncountered) { if (address - previousAddress == 7 && address % 0x10 == 0) warningHash.add( line, "Warning between " + ToyWord.toHexString(previousAddress).substring(2) + " and " + ToyWord.toHexString(address).substring(2) + ":", "There are undefined lines. And really seems that you've " + "forgotten that addresses are in base 16. For instance, the " + "number after 0x19 is not 0x20 it's 0x1A. If this really is " + "the space between functions, please leave a function comment " + "(Tools|Insert Function Comment)." ); else warningHash.add( line, "Warning between " + ToyWord.toHexString(previousAddress).substring(2) + " and " + ToyWord.toHexString(address).substring(2) + ":", "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)." ); } //check for attempts to assign a value to a constant except //with a 1000 command if (rd == 0 && (op >= 1 && op <= 8 || op == 0xA || op == 0xF) && !(op == 1 && rd == 0 && rs == 0 && rt == 0)) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Register 0 is a constant you cannot change its value." ); //check for non-zero assignments to characters which arn't used if (op == 0 && (rd != 0 || rs != 0 || rt != 0)) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Operator 0 (halt) does not require an d, s, or t. It is " + "conventional to halt a program with \"0000\"." ); if (op == 0xA && rs != 0) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Operator A (load indirect) does not require an s. It is " + "conventional to assign the 3rd digit to '0'." ); if (op == 0xB && rs != 0) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Operator B (store indirect) does not require an s. It is " + "conventional to assign the 3rd digit to '0'." ); if (op == 0xE && (rs != 0 || rt != 0)) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Operator E (jump register) does not require an s or t. It is " + "conventional to terminate such a command with two '0's." ); //check for simple infinite loops if (op == 0xC && rs * 16 + rt == address) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "This command is an infinite loop." ); if ((op == 0xD || op == 0xF) && rs * 16 + rt == address + 1) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "This command could be an infinite loop." ); //check for lines which are guaranteed not to do a thing if ((op == 0xC || op == 0xD) && rs * 16 + rt == address + 1) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "This is a command to jump to the next line. In other words, " + "this line appears to serve no purpose." ); if (op == 0xD && rd == 0) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "Operator D (branch positive) is guaranteed not to branch since " + "R[0] is always 0. In other words, his line appears to serve no " + "purpose." ); if ((op == 1 || op == 3) && (rd == rs && rt == 0 || rd == rt && rs == 0) && !(op == 1 && rd == 0 && rs == 0 && rt == 0) || op == 2 && rd == rs && rt == 0) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "This command is guaranteed not to change R[" + ToyWord.convertToHexDigit(rd) + "] because R[0] is always 0; " + "In other words, this line appears to serve no purpose." ); if (op == 4 && rd == rs && rs == rt || (op == 5 || op == 6) && rd == rs && rt == 0) warningHash.add( line, "Warning at " + ToyWord.toHexString(address).substring(2) + ":", "This command is guaranteed not to change R[" + ToyWord.convertToHexDigit(rd) + "]. In other words, this " + "line appears to serve no purpose." ); } parsedVirtualMachine.setMem( address, new ToyWord((short)((op << 12) | (rd << 8) | (rs << 4) | rt)) ); previousAddress = address; functionEncountered = false; } else { //if it's not a hex command, maybe it's a function comment if (charCtr + 12 < text.length() && text.charAt(charCtr) == '/' && text.charAt(charCtr + 1) == '/' && text.substring(charCtr).startsWith("// Function ")) functionEncountered = true; } //advance to and past the next newline while (charCtr < text.length() && text.charAt(charCtr) != '\n') charCtr++; charCtr++; //advance to first non-whitespace character or newline while (charCtr < text.length() && Character.isWhitespace(text.charAt(charCtr)) && text.charAt(charCtr) != '\n') charCtr++; line++; } //copy the parsedVirtualMachine over to the initialVirtualMachine //(note how we're leaving the stdin of initialVirtualMachine intact by // not resetting it) for (int ctr = 0; ctr < ToyVirtualMachine.MEM_CARDINALITY; ctr++) initialVirtualMachine.setMem( ctr, parsedVirtualMachine.getMem(ctr) ); reset(); hasFatalError = false; return warningHash; } //////////////////////////////////////////////////////////////////////////// // getDebugHash() public ToyDebugHash getDebugHash() { int stringCtr = 0; boolean printedPreviousLine = false; String codeLine; ToyDebugHash answer = new ToyDebugHash(); //go through each line and print the defined ones //if a line is undefined and the previous line was defined, print a "..." //but if a line was undefined and it's predecessor was undefined, don't //print a "..." //(this will make it so that only one "..." is printed for every block // of undefined lines) for (short ctr = 0; ctr <= 0xFF; ctr++) { if (virtualMachine.getMem(ctr).isDefined()) { codeLine = Integer.toHexString(ctr).toUpperCase(); if (codeLine.length() < 2) codeLine = "0" + codeLine; codeLine += ": "; codeLine += virtualMachine.getMem(ctr).toHexString() + " "; if (ctr < 0x10) //if we're in the low memory range, also print the binary and //decimal translations of the mem[] contents codeLine += "(" + virtualMachine.getMem(ctr).toFormattedBinaryString() + ", " + virtualMachine.getMem(ctr).toDecimalString() + ")"; else //if we're in the upper memory range, also print the pseudocode //equilvanet of the mem[] contents codeLine += virtualMachine.getMem(ctr).toPseudoCodeString(); answer.add(ctr, codeLine); printedPreviousLine = true; } else { if (printedPreviousLine) { answer.add(ctr, "..."); printedPreviousLine = false; } } } return answer; } //////////////////////////////////////////////////////////////////////////// // getModifiedText() returns either a modification of a specified string, or // a modification of the most recently parsed string. This modification // will alter the text just enough so that it will correctly parse into the // current state of the virtual machine public String getModifiedText() { return getModifiedText(text); } public String getModifiedText(String newText) { int charCtr; short addr, data; short op, rd, rs, rt; short addrCtr; //first, go through the text, and update all entries that are already //there //advance to first non-whitespace character or newline charCtr = 0; while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; while (charCtr + 7 < newText.length()) { if (ToyWord.isHexDigit(newText.charAt(charCtr)) && ToyWord.isHexDigit(newText.charAt(charCtr + 1)) && newText.charAt(charCtr + 2) == ':' && newText.charAt(charCtr + 3) == ' ' && ToyWord.isHexDigit(newText.charAt(charCtr + 4)) && ToyWord.isHexDigit(newText.charAt(charCtr + 5)) && ToyWord.isHexDigit(newText.charAt(charCtr + 6)) && ToyWord.isHexDigit(newText.charAt(charCtr + 7))) { addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10 + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1))); newText = newText.substring(0, charCtr) + ToyWord.toHexString(addr).substring(2) + ": " + virtualMachine.getMem(addr).toHexString() + newText.substring(charCtr + 8); } while (charCtr < newText.length() && newText.charAt(charCtr) != '\n') charCtr++; charCtr++; //advance to first non-whitespace character or newline while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; } //next, go through the text, and add any new defined entries addrCtr = 0; //find our first defined mem slot while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY && !virtualMachine.getMem(addrCtr).isDefined()) addrCtr++; //advance to first non-whitespace character or newline charCtr = 0; while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY && charCtr + 7 < newText.length()) { if (ToyWord.isHexDigit(newText.charAt(charCtr)) && ToyWord.isHexDigit(newText.charAt(charCtr + 1)) && newText.charAt(charCtr + 2) == ':' && newText.charAt(charCtr + 3) == ' ' && ToyWord.isHexDigit(newText.charAt(charCtr + 4)) && ToyWord.isHexDigit(newText.charAt(charCtr + 5)) && ToyWord.isHexDigit(newText.charAt(charCtr + 6)) && ToyWord.isHexDigit(newText.charAt(charCtr + 7))) { addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10 + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1))); //if the next defined mem slot correlates with the next //definition in the code, find the next mem slot if (addrCtr == addr) { addrCtr++; while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY && !virtualMachine.getMem(addrCtr).isDefined()) addrCtr++; } //if the next defined mem slot is smaller than the next //definition in the code... if (addrCtr < addr) { //insert the string of that mem slot into the code newText = newText.substring(0, charCtr) + ToyWord.toHexString(addrCtr).substring(2) + ": " + virtualMachine.getMem(addrCtr).toHexString() + "\n" + newText.substring(charCtr); //advance to the next defined mem slot addrCtr++; while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY && !virtualMachine.getMem(addrCtr).isDefined()) addrCtr++; } } //advance to the next line while (charCtr < newText.length() && newText.charAt(charCtr) != '\n') charCtr++; charCtr++; //advance to first non-whitespace character or newline while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; } //we arn't done yet! tag along any remianing defined mem slots to the //end of the string if (addrCtr < ToyVirtualMachine.MEM_CARDINALITY) { newText = newText.trim(); newText += "\n\n"; } while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY) { int oldAddrCtr = addrCtr; newText += ToyWord.toHexString(addrCtr).substring(2) + ": " + virtualMachine.getMem(addrCtr).toHexString() + "\n"; addrCtr++; while (addrCtr < ToyVirtualMachine.MEM_CARDINALITY && !virtualMachine.getMem(addrCtr).isDefined()) addrCtr++; if (addrCtr - oldAddrCtr > 1 && addrCtr < ToyVirtualMachine.MEM_CARDINALITY) newText += "\n"; } //finally, go through the newText, and add pseudocode to the code lines //that are of the form "##: ####*\n" where * is any amount of whitespace //or COMMENT_WIDTH characters that begin with three spaces and end with //at least one space (no newlines in between obviously) //advance to first non-whitespace character or newline charCtr = 0; while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; while (charCtr + 7 < newText.length()) { if (ToyWord.isHexDigit(newText.charAt(charCtr)) && ToyWord.isHexDigit(newText.charAt(charCtr + 1)) && newText.charAt(charCtr + 2) == ':' && newText.charAt(charCtr + 3) == ' ' && ToyWord.isHexDigit(newText.charAt(charCtr + 4)) && ToyWord.isHexDigit(newText.charAt(charCtr + 5)) && ToyWord.isHexDigit(newText.charAt(charCtr + 6)) && ToyWord.isHexDigit(newText.charAt(charCtr + 7))) { int endCtr = charCtr + 8; //try the all whitespace condition while (endCtr < newText.length() && Character.isWhitespace(newText.charAt(endCtr)) && newText.charAt(endCtr) != '\n') endCtr++; //try the COMMENT_WITH character condition if (endCtr < newText.length() && newText.charAt(endCtr) != '\n') { while (endCtr < newText.length() && endCtr - (charCtr + 8) < COMMENT_WIDTH && newText.charAt(endCtr) != '\n') endCtr++; } addr = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr)) * 0x10 + ToyWord.convertFromHexDigit(newText.charAt(charCtr + 1))); data = (short)(ToyWord.convertFromHexDigit(newText.charAt(charCtr + 4)) << 12 | ToyWord.convertFromHexDigit(newText.charAt(charCtr + 5)) << 8 | ToyWord.convertFromHexDigit(newText.charAt(charCtr + 6)) << 4 | ToyWord.convertFromHexDigit(newText.charAt(charCtr + 7))); if (addr < 0x10) { String comment = " " + ToyWord.toString(data); while (comment.length() < COMMENT_WIDTH) comment += " "; newText = newText.substring(0, charCtr + 8) + comment + newText.substring(endCtr); } else { String comment = " " + ToyWord.toPseudoCodeString(data); while (comment.length() < COMMENT_WIDTH) comment += " "; newText = newText.substring(0, charCtr + 8) + comment + newText.substring(endCtr); } } while (charCtr < newText.length() && newText.charAt(charCtr) != '\n') charCtr++; charCtr++; //advance to first non-whitespace character or newline while (charCtr < newText.length() && Character.isWhitespace(newText.charAt(charCtr)) && newText.charAt(charCtr) != '\n') charCtr++; } return newText; } }