// Scans the given file and creates symbols for its functions & label names. private void updateSymbolTable(File file, Hashtable symbols, Hashtable functions) throws ProgramException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file.getAbsolutePath())); } catch (FileNotFoundException fnfe) { throw new ProgramException("file " + file.getName() + " does not exist"); } String line; String currentFunction = null; String label; int lineNumber = 0; isSlashStar = false; try { while ((line = unCommentLine(reader.readLine())) != null) { lineNumber++; if (!line.trim().equals("")) { if (line.startsWith("function ")) { StringTokenizer tokenizer = new StringTokenizer(line); tokenizer.nextToken(); currentFunction = tokenizer.nextToken(); if (symbols.containsKey(currentFunction)) throw new ProgramException("subroutine " + currentFunction + " already exists"); functions.put(currentFunction, new Short(nextPC)); symbols.put(currentFunction, new Short(nextPC)); } else if (line.startsWith("label ")) { StringTokenizer tokenizer = new StringTokenizer(line); tokenizer.nextToken(); label = currentFunction + "$" + tokenizer.nextToken(); symbols.put(label, new Short((short) (nextPC + 1))); } nextPC++; } } reader.close(); } catch (IOException ioe) { throw new ProgramException("Error while reading from file"); } catch (NoSuchElementException nsee) { throw new ProgramException("In line " + lineNumber + ": unexpected end of command"); } if (isSlashStar) { throw new ProgramException("Unterminated /* comment at end of file"); } }
/** * 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); }