public short getAddress(String functionName) throws ProgramException { Short address = (Short) functions.get(functionName); if (address != null) { return address.shortValue(); } else { String className = functionName.substring(0, functionName.indexOf(".")); if (staticRange.get(className) == null) { // The class is not implemented by a VM file - search for a // built-in implementation later. Display a popup to confirm // this as this is not a feature from the book but a later // addition. if (builtInAccessStatus == BUILTIN_ACCESS_UNDECIDED) { if (hasGUI && gui.confirmBuiltInAccess()) { builtInAccessStatus = BUILTIN_ACCESS_AUTHORIZED; } else { builtInAccessStatus = BUILTIN_ACCESS_DENIED; } } if (builtInAccessStatus == BUILTIN_ACCESS_AUTHORIZED) { return BUILTIN_FUNCTION_ADDRESS; } } // Either: // 1.The class is implemented by a VM file and no implementation // for the function is found - don't override with built-in // - or - // 2.The user did not authorize using built-in implementations. throw new ProgramException( className + ".vm not found " + "or function " + functionName + " not found in " + className + ".vm"); } }
/** * Creates a vm program. If the given file is a dir, creates a program composed of the vm files in * the dir. The vm files are scanned twice: in the first scan a symbol table (that maps function & * label names into addresses) is built. In the second scan, the instructions array is built. * Throws ProgramException if an error occurs while loading the program. */ public void loadProgram(String fileName) throws ProgramException { File file = new File(fileName); if (!file.exists()) throw new ProgramException("cannot find " + fileName); File[] files; if (file.isDirectory()) { files = file.listFiles(new HackFileFilter(".vm")); if (files == null || files.length == 0) throw new ProgramException("No vm files found in " + fileName); } else files = new File[] {file}; if (displayChanges) gui.showMessage("Loading..."); // First scan staticRange.clear(); functions.clear(); builtInAccessStatus = BUILTIN_ACCESS_UNDECIDED; Hashtable symbols = new Hashtable(); nextPC = 0; for (int i = 0; i < files.length; i++) { String name = files[i].getName(); String className = name.substring(0, name.indexOf(".")); // put some dummy into static range - just to tell the function // getAddress in the second pass which classes exist staticRange.put(className, new Boolean(true)); try { updateSymbolTable(files[i], symbols, functions); } catch (ProgramException pe) { if (displayChanges) gui.hideMessage(); throw new ProgramException(name + ": " + pe.getMessage()); } } boolean addCallBuiltInSysInit = false; if ((file.isDirectory() || symbols.get("Main.main") != null) && symbols.get("Sys.init") == null) { // If the program is in multiple files or there's a Main.main // function it is assumed that it should be run by calling Sys.init. // If no Sys.init is found, add an invisible line with a call // to Sys.init to start on - the builtin version will be called. addCallBuiltInSysInit = true; getAddress("Sys.init"); // confirm calling the built-in Sys.init ++nextPC; // A "call Sys.init 0" line will be added } instructions = new VMEmulatorInstruction[nextPC + 4]; // Second scan nextPC = 0; currentStaticIndex = Definitions.VAR_START_ADDRESS; for (int i = 0; i < files.length; i++) { String name = files[i].getName(); String className = name.substring(0, name.indexOf(".")); largestStaticIndex = -1; int[] range = new int[2]; range[0] = currentStaticIndex; try { // functions is not passed as an argument since it is accessed // through getAddress() buildProgram(files[i], symbols); } catch (ProgramException pe) { if (displayChanges) gui.hideMessage(); throw new ProgramException(name + ": " + pe.getMessage()); } currentStaticIndex += largestStaticIndex + 1; range[1] = currentStaticIndex - 1; staticRange.put(className, range); } instructionsLength = visibleInstructionsLength = nextPC; if (builtInAccessStatus == BUILTIN_ACCESS_AUTHORIZED) { // Add some "invisible" code in the end to make everything work instructionsLength += 4; if (addCallBuiltInSysInit) { instructionsLength += 1; } short indexInInvisibleCode = 0; // Add a jump to the end (noone should get here since // both calls to built-in functions indicate that // that this is a function-based program and not a script // a-la proj7, but just to be on the safe side...). instructions[nextPC] = new VMEmulatorInstruction( HVMInstructionSet.GOTO_CODE, (short) instructionsLength, indexInInvisibleCode); instructions[nextPC].setStringArg("afterInvisibleCode"); nextPC++; // Add a small infinite loop for built-in // methods to call (for example when Sys.halt is // called it must call a non-built-in infinite loop // because otherwise the current script would not // finish running - a problem for the OS tests. instructions[nextPC] = new VMEmulatorInstruction(HVMInstructionSet.LABEL_CODE, (short) -1); instructions[nextPC].setStringArg("infiniteLoopForBuiltIns"); nextPC++; infiniteLoopForBuiltInsAddress = nextPC; instructions[nextPC] = new VMEmulatorInstruction(HVMInstructionSet.GOTO_CODE, nextPC, ++indexInInvisibleCode); instructions[nextPC].setStringArg("infiniteLoopForBuiltIns"); nextPC++; if (addCallBuiltInSysInit) { // Add a call to the built-in Sys.init instructions[nextPC] = new VMEmulatorInstruction( HVMInstructionSet.CALL_CODE, getAddress("Sys.init"), (short) 0, ++indexInInvisibleCode); instructions[nextPC].setStringArg("Sys.init"); startAddress = nextPC; nextPC++; } // Add the label that the first invisible code line jumps to instructions[nextPC] = new VMEmulatorInstruction(HVMInstructionSet.LABEL_CODE, (short) -1); instructions[nextPC].setStringArg("afterInvisibleCode"); nextPC++; } if (!addCallBuiltInSysInit) { Short sysInitAddress = (Short) symbols.get("Sys.init"); if (sysInitAddress == null) // Single file, no Sys.init - start at 0 startAddress = 0; else // Implemented Sys.init - start there startAddress = sysInitAddress.shortValue(); } if (displayChanges) gui.hideMessage(); nextPC = startAddress; setGUIContents(); notifyProgramListeners(ProgramEvent.LOAD, fileName); }
/** * Returns the static variable address range of the given class name, in the form of a 2-elements * array {startAddress, endAddress}. If unknown class name, returns null. */ public int[] getStaticRange(String className) { return (int[]) staticRange.get(className); }
// Scans the given file and creates symbols for its functions & label names. private void buildProgram(File file, Hashtable symbols) throws ProgramException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file.getAbsolutePath())); } catch (FileNotFoundException fnfe) { throw new ProgramException("file does not exist"); } int lineNumber = 0; String line; String label; String instructionName; String currentFunction = null; short indexInFunction = 0; byte opCode; short arg0, arg1; short pc = nextPC; HVMInstructionSet instructionSet = HVMInstructionSet.getInstance(); isSlashStar = false; try { while ((line = unCommentLine(reader.readLine())) != null) { lineNumber++; if (!line.trim().equals("")) { StringTokenizer tokenizer = new StringTokenizer(line); instructionName = tokenizer.nextToken(); opCode = instructionSet.instructionStringToCode(instructionName); if (opCode == HVMInstructionSet.UNKNOWN_INSTRUCTION) throw new ProgramException( "in line " + lineNumber + ": unknown instruction - " + instructionName); switch (opCode) { case HVMInstructionSet.PUSH_CODE: String segment = tokenizer.nextToken(); try { arg0 = translateSegment(segment, instructionSet, file.getName()); } catch (ProgramException pe) { throw new ProgramException("in line " + lineNumber + pe.getMessage()); } arg1 = Short.parseShort(tokenizer.nextToken()); if (arg1 < 0) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); if (arg0 == HVMInstructionSet.STATIC_SEGMENT_CODE && arg1 > largestStaticIndex) largestStaticIndex = arg1; instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction); break; case HVMInstructionSet.POP_CODE: int n = tokenizer.countTokens(); segment = tokenizer.nextToken(); try { arg0 = translateSegment(segment, instructionSet, file.getName()); } catch (ProgramException pe) { throw new ProgramException("in line " + lineNumber + pe.getMessage()); } arg1 = Short.parseShort(tokenizer.nextToken()); if (arg1 < 0) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); if (arg0 == HVMInstructionSet.STATIC_SEGMENT_CODE && arg1 > largestStaticIndex) largestStaticIndex = arg1; instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction); break; case HVMInstructionSet.FUNCTION_CODE: currentFunction = tokenizer.nextToken(); indexInFunction = 0; arg0 = Short.parseShort(tokenizer.nextToken()); if (arg0 < 0) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction); instructions[pc].setStringArg(currentFunction); break; case HVMInstructionSet.CALL_CODE: String functionName = tokenizer.nextToken(); try { arg0 = getAddress(functionName); } catch (ProgramException pe) { throw new ProgramException("in line " + lineNumber + ": " + pe.getMessage()); } arg1 = Short.parseShort(tokenizer.nextToken()); if (arg1 < 0 || ((arg0 < 0 || arg0 > Definitions.ROM_SIZE) && arg0 != BUILTIN_FUNCTION_ADDRESS)) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction); instructions[pc].setStringArg(functionName); break; case HVMInstructionSet.LABEL_CODE: label = currentFunction + "$" + tokenizer.nextToken(); instructions[pc] = new VMEmulatorInstruction(opCode, (short) (-1)); instructions[pc].setStringArg(label); indexInFunction--; // since Label is not a "physical" instruction break; case HVMInstructionSet.GOTO_CODE: label = currentFunction + "$" + tokenizer.nextToken(); Short labelAddress = (Short) symbols.get(label); if (labelAddress == null) throw new ProgramException("in line " + lineNumber + ": Unknown label - " + label); arg0 = labelAddress.shortValue(); if (arg0 < 0 || arg0 > Definitions.ROM_SIZE) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction); instructions[pc].setStringArg(label); break; case HVMInstructionSet.IF_GOTO_CODE: label = currentFunction + "$" + tokenizer.nextToken(); labelAddress = (Short) symbols.get(label); if (labelAddress == null) throw new ProgramException("in line " + lineNumber + ": Unknown label - " + label); arg0 = labelAddress.shortValue(); if (arg0 < 0 || arg0 > Definitions.ROM_SIZE) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction); instructions[pc].setStringArg(label); break; // All other instructions have either 1 or 0 arguments and require no // special treatment default: if (tokenizer.countTokens() == 0) { instructions[pc] = new VMEmulatorInstruction(opCode, indexInFunction); } else { arg0 = Short.parseShort(tokenizer.nextToken()); if (arg0 < 0) throw new ProgramException( "in line " + lineNumber + ": Illegal argument - " + line); instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction); } break; } // check end of command if (tokenizer.hasMoreTokens()) throw new ProgramException("in line " + lineNumber + ": Too many arguments - " + line); pc++; indexInFunction++; } nextPC = pc; } reader.close(); } catch (IOException ioe) { throw new ProgramException("Error while reading from file"); } catch (NumberFormatException nfe) { throw new ProgramException("Illegal 16-bit value"); } catch (NoSuchElementException nsee) { throw new ProgramException("In line " + lineNumber + ": unexpected end of command"); } if (isSlashStar) { throw new ProgramException("Unterminated /* comment at end of file"); } }