// 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); }
// 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"); } }