/**
* ******************************************************************************
* The contents of this file are subject to the GNU General Public License * (GPL) Version 2 or
* later (the "License"); you may not use this file except * in compliance with the License. You may
* obtain a copy of the License at * http://www.gnu.org/copyleft/gpl.html * * Software distributed
* under the License is distributed on an "AS IS" basis, * without warranty of any kind, either
* expressed or implied. See the License * for the specific language governing rights and
* limitations under the * License. * * This file was originally developed as part of the software
* suite that * supports the book "The Elements of Computing Systems" by Nisan and Schocken, * MIT
* Press 2005. If you modify the contents of this file, please document and * mark your changes
* clearly, for the benefit of others. *
* ******************************************************************************
*/
package Hack.Assembler;
import Hack.ComputerParts.TextFileEvent;
import Hack.ComputerParts.TextFileGUI;
import Hack.Translators.HackTranslator;
import Hack.Translators.HackTranslatorEvent;
import Hack.Translators.HackTranslatorException;
import Hack.Utilities.Conversions;
import Hack.Utilities.Definitions;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;
/**
* A translator from assmebly (.asm) to hack machine language (.hack)
*/
public class HackAssembler extends HackTranslator {
// the reader of the comparison file
private BufferedReader comparisonReader;
// the name of the comparison .hack file
private String comparisonFileName;
// the symbol table
private Hashtable<String, Short> symbolTable;
// The comarison program array
private short[] comparisonProgram;
// The HackAssembler translator;
private HackAssemblerTranslator translator;
// Index of the next location for unrecognized labels
private short varIndex;
/**
* Constructs a new HackAssembler with the size of the program memory and .asm source file name.
* The given null value will be used to fill the program initially. The compiled program can
* later be fetched using the getProgram() method. If save is true, the compiled program will be
* saved automatically into a ".hack" file that will have the same name as the source but with
* the .hack extension.
*/
public HackAssembler(String fileName, int size, short nullValue, boolean save)
throws HackTranslatorException {
super(fileName, size, nullValue, save);
}
/**
* Constructs a new HackAssembler with the size of the program memory. The given null value will
* be used to fill the program initially. A non null sourceFileName specifies a source file to
* be loaded. The gui is assumed to be not null.
*/
public HackAssembler(HackAssemblerGUI gui, int size, short nullValue, String sourceFileName)
throws HackTranslatorException {
super(gui, size, nullValue, sourceFileName);
gui.enableLoadComparison();
gui.hideComparison();
}
@Override
protected String getSourceExtension() {
return "asm";
}
@Override
protected String getDestinationExtension() {
return "hack";
}
@Override
protected String getName() {
return "Assembler";
}
@Override
protected void init(int size, short nullValue) {
super.init(size, nullValue);
translator = HackAssemblerTranslator.getInstance();
}
// Checks the given comparison file name and throws an AssemblerException
// if not legal.
private void checkComparisonFile(String fileName) throws HackTranslatorException {
if (!fileName.endsWith("." + getDestinationExtension())) {
throw new HackTranslatorException(fileName + " is not a ." + getDestinationExtension()
+ " file");
}
File file = new File(fileName);
if (!file.exists()) {
throw new HackTranslatorException("File " + fileName + " does not exist");
}
}
@Override
protected void restartCompilation() {
super.restartCompilation();
varIndex = Definitions.VAR_START_ADDRESS;
if (getGui() != null) {
((HackAssemblerGUI) getGui()).enableLoadComparison();
}
}
// opens the comparison file for reading.
private void resetComparisonFile() throws HackTranslatorException {
try {
comparisonReader = new BufferedReader(new FileReader(comparisonFileName));
if (getGui() != null) {
TextFileGUI comp = ((HackAssemblerGUI) getGui()).getComparison();
comp.reset();
comp.setContents(comparisonFileName);
comparisonProgram = new short[comp.getNumberOfLines()];
for (int i = 0; i < comp.getNumberOfLines(); i++) {
if (comp.getLineAt(i).length() != Definitions.BITS_PER_WORD) {
throw new HackTranslatorException("Error in file " + comparisonFileName + ": Line " + i + " does not contain exactly " + Definitions.BITS_PER_WORD + " characters");
}
try {
comparisonProgram[i] = (short) Conversions.binaryToInt(comp.getLineAt(i));
} catch (NumberFormatException nfe) {
throw new HackTranslatorException("Error in file " + comparisonFileName + ": Line " + i + " does not contain only 1/0 characters");
}
}
}
} catch (IOException ioe) {
throw new HackTranslatorException("Error reading from file " + comparisonFileName);
}
}
@Override
protected void initSource() throws HackTranslatorException {
generateSymbolTable();
}
// Generates The symbol table by attaching each label with it's appropriate
// value according to it's location in the program
private void generateSymbolTable() throws HackTranslatorException {
symbolTable = Definitions.getInstance().getAddressesTable();
short pc = 0;
String line;
String label;
try {
try (BufferedReader sourceReader = new BufferedReader(new FileReader(getSourceFileName()))) {
while ((line = sourceReader.readLine()) != null) {
AssemblyLineTokenizer input = new AssemblyLineTokenizer(line);
if (!input.isEnd()) {
if (input.isToken("(")) {
input.advance(true);
label = input.token();
input.advance(true);
if (!input.isToken(")")) {
error("')' expected");
}
input.ensureEnd();
symbolTable.put(label, new Short(pc));
} else if (input.contains("[")) {
pc += 2;
} else {
pc++;
}
}
}
}
} catch (IOException ioe) {
throw new HackTranslatorException("Error reading from file " + getSourceFileName());
}
}
@Override
protected void initCompilation() throws HackTranslatorException {
if (getGui() != null && (isInFullCompilation() || !isCompilationStarted())) {
((HackAssemblerGUI) getGui()).disableLoadComparison();
}
}
@Override
protected void successfulCompilation() throws HackTranslatorException {
if (comparisonReader == null) {
super.successfulCompilation();
} else {
if (getGui() != null) {
((HackAssemblerGUI) getGui()).displayMessage("File compilation & comparison succeeded", false);
}
}
}
@Override
protected int[] compileLineAndCount(String line) throws HackTranslatorException {
int[] compiledRange = super.compileLineAndCount(line);
// check comparison
if (compiledRange != null && comparisonReader != null) {
int length = compiledRange[1] - compiledRange[0] + 1;
boolean compare = compare(compiledRange);
if (isInFullCompilation()) {
if (!compare) {
if (getGui() != null) {
setProgramSize(getDestPC() + length - 1);
showProgram(getProgramSize());
getGui().getSource().addHighlight(getSourcePC(), true);
getGui().getDestination().addHighlight(getDestPC() - 1, true);
((HackAssemblerGUI) getGui()).getComparison().addHighlight(getDestPC() - 1, true);
getGui().enableRewind();
getGui().enableLoadSource();
}
}
} else {
if (compare) {
((HackAssemblerGUI) getGui()).getComparison().addHighlight(getDestPC() + length - 2, true);
} else {
getGui().getDestination().addHighlight(getDestPC() - 1, true);
((HackAssemblerGUI) getGui()).getComparison().addHighlight(getDestPC() - 1, true);
}
}
if (!compare) {
throw new HackTranslatorException("Comparison failure");
}
}
return compiledRange;
}
// Compares the given commands to the next commands in the comparison file.
private boolean compare(int[] compiledRange) {
boolean result = true;
int length = compiledRange[1] - compiledRange[0] + 1;
for (int i = 0; i < length && result; i++) {
result = (getProgram()[compiledRange[0] + i] == comparisonProgram[compiledRange[0] + i]);
}
return result;
}
@Override
protected String getCodeString(short code, int pc, boolean display) {
return Conversions.decimalToBinary(code, 16);
}
@Override
protected void fastForward() {
((HackAssemblerGUI) getGui()).disableLoadComparison();
super.fastForward();
}
@Override
protected void hidePointers() {
super.hidePointers();
if (comparisonReader != null) {
((HackAssemblerGUI) getGui()).getComparison().clearHighlights();
}
}
@Override
protected void end(boolean hidePointers) {
super.end(hidePointers);
((HackAssemblerGUI) getGui()).disableLoadComparison();
}
@Override
protected void stop() {
super.stop();
((HackAssemblerGUI) getGui()).disableLoadComparison();
}
@Override
protected void rewind() {
super.rewind();
if (comparisonReader != null) {
((HackAssemblerGUI) getGui()).getComparison().clearHighlights();
((HackAssemblerGUI) getGui()).getComparison().hideSelect();
}
}
// If the line is a label, returns null.
@Override
protected void compileLine(String line) throws HackTranslatorException {
try {
AssemblyLineTokenizer input = new AssemblyLineTokenizer(line);
if (!input.isEnd() && !input.isToken("(")) {
if (input.isToken("@")) {
input.advance(true);
boolean numeric = true;
String label = input.token();
input.ensureEnd();
try {
Short.parseShort(label);
} catch (NumberFormatException nfe) {
numeric = false;
}
if (!numeric) {
Short address = symbolTable.get(label);
if (address == null) {
address = new Short(varIndex++);
symbolTable.put(label, address);
}
addCommand(translator.textToCode("@" + address.shortValue()));
} else {
addCommand(translator.textToCode(line));
}
} else { // try to compile normaly, if error - try to compile as compact assembly
try {
addCommand(translator.textToCode(line));
} catch (AssemblerException ae) {
int openAddressPos = line.indexOf('[');
if (openAddressPos >= 0) {
int lastPos = line.lastIndexOf('[');
int closeAddressPos = line.indexOf(']');
if (openAddressPos != lastPos || openAddressPos > closeAddressPos
|| openAddressPos + 1 == closeAddressPos) {
throw new AssemblerException(
"Illegal use of the [] notation");
}
String address = line.substring(openAddressPos + 1, closeAddressPos);
compileLine("@" + address);
compileLine(line.substring(0, openAddressPos).concat(
line.substring(closeAddressPos + 1)));
} else {
throw new AssemblerException(ae.getMessage());
}
}
}
}
} catch (IOException ioe) {
throw new HackTranslatorException("Error reading from file " + getSourceFileName());
} catch (AssemblerException ae) {
throw new HackTranslatorException(ae.getMessage(), getSourcePC());
}
}
@Override
protected void finalizeCompilation() {
}
@Override
public void rowSelected(TextFileEvent event) {
super.rowSelected(event);
int[] range = rowIndexToRange(event.getRowIndex());
if (range != null) {
if (comparisonReader != null) {
((HackAssemblerGUI) getGui()).getComparison().select(range[0], range[1]);
}
} else {
if (comparisonReader != null) {
((HackAssemblerGUI) getGui()).getComparison().hideSelect();
}
}
}
@Override
public void actionPerformed(HackTranslatorEvent event) {
super.actionPerformed(event);
switch (event.getAction()) {
case HackTranslatorEvent.SOURCE_LOAD:
comparisonFileName = "";
comparisonReader = null;
((HackAssemblerGUI) getGui()).setComparisonName("");
((HackAssemblerGUI) getGui()).hideComparison();
break;
case HackAssemblerEvent.COMPARISON_LOAD:
clearMessage();
String fileName = (String) event.getData();
try {
checkComparisonFile(fileName);
comparisonFileName = fileName;
saveWorkingDir(new File(fileName));
resetComparisonFile();
((HackAssemblerGUI) getGui()).showComparison();
} catch (HackTranslatorException ae) {
getGui().displayMessage(ae.getMessage(), true);
}
break;
}
}
}