Early refactoring of compiler.

This commit is contained in:
Robert Greene 2003-06-03 05:47:57 +00:00
parent 370126207a
commit 79e792984e

View File

@ -24,13 +24,16 @@ import com.webcodepro.applecommander.util.ApplesoftToken;
import com.webcodepro.applecommander.util.ApplesoftTokenizer; import com.webcodepro.applecommander.util.ApplesoftTokenizer;
import com.webcodepro.applecommander.util.ApplesoftTokens; import com.webcodepro.applecommander.util.ApplesoftTokens;
import java.io.ByteArrayOutputStream; import java.io.IOException;
import java.io.PrintWriter; import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
/** /**
* Compile the given file as an Applesoft file. * Compile the given file as an Applesoft file.
@ -40,13 +43,30 @@ import java.util.Map;
*/ */
public class ApplesoftCompiler implements ApplesoftTokens { public class ApplesoftCompiler implements ApplesoftTokens {
private ApplesoftTokenizer tokenizer; private ApplesoftTokenizer tokenizer;
private ApplesoftToken tokenAlreadySeen;
private StringBuffer sourceAssembly = new StringBuffer();
private StringBuffer sourceLine = new StringBuffer();
private List stringVariables = new ArrayList(); /**
private List integerVariables = new ArrayList(); * Contains a list of all known Apple ROM addresses that may be used
private List floatVariables = new ArrayList(); * by the compiled program. This map is keyed by the name of the
private Map stringConstants = new HashMap(); * address and the value is the address.
private Map integerConstants = new HashMap(); */
private Map floatConstants = new HashMap(); private Map knownAddresses = new HashMap();
/**
* Lists the names of all addresses used by the compiled program.
* To identify the value, use the knownAddresses map.
*/
private List usedAddresses = new ArrayList();
/**
* Contains a list of all variables declared or used by the
* program.
*/
private List variables = new ArrayList();
/**
* Dynamically created map of commands to Methods.
*/
private Map commandMethods = new HashMap();
/** /**
* Constructor for ApplesoftCompiler. * Constructor for ApplesoftCompiler.
@ -54,296 +74,406 @@ public class ApplesoftCompiler implements ApplesoftTokens {
public ApplesoftCompiler(FileEntry fileEntry) { public ApplesoftCompiler(FileEntry fileEntry) {
super(); super();
tokenizer = new ApplesoftTokenizer(fileEntry); tokenizer = new ApplesoftTokenizer(fileEntry);
initializeKnownAddresses();
} }
/** /**
* Compile the given FileEntry and return the assembly code. * Load known memory addresses from AppleMemoryAddresses.properties.
*/ */
public byte[] compile() { protected void initializeKnownAddresses() {
ByteArrayOutputStream assemblyStream = new ByteArrayOutputStream(); InputStream inputStream =
PrintWriter assemblyWriter = new PrintWriter(assemblyStream); getClass().getResourceAsStream("AppleMemoryAddresses.properties");
StringBuffer sourceLine = new StringBuffer(); Properties properties = new Properties();
StringBuffer sourceAssembly = new StringBuffer(); try {
while (tokenizer.hasMoreTokens()) { properties.load(inputStream);
ApplesoftToken token = tokenizer.getNextToken(); Enumeration keys = properties.keys();
if (token == null) { while (keys.hasMoreElements()) {
break; String key = (String) keys.nextElement();
} else if (token.isLineNumber()) { knownAddresses.put(key, properties.getProperty(key));
if (sourceLine.length() > 0) {
assemblyWriter.println(sourceLine.toString());
assemblyWriter.println(sourceAssembly.toString());
sourceLine.setLength(0);
sourceAssembly.setLength(0);
} }
sourceAssembly.append("LINE"); } catch (IOException ex) {
sourceAssembly.append(token.getLineNumber()); ex.printStackTrace();
sourceAssembly.append(":\n"); }
}
/**
* Answers true if there are more tokens to process.
*/
protected boolean hasMoreTokens() {
return peekToken() != null;
}
/**
* Get the next token.
*/
protected ApplesoftToken nextToken() {
ApplesoftToken token = tokenAlreadySeen;
if (tokenAlreadySeen != null) {
tokenAlreadySeen = null;
} else {
token = tokenizer.getNextToken();
}
if (token == null) {
// No more tokens
} else if (token.isLineNumber()) {
sourceLine.append("* "); sourceLine.append("* ");
sourceLine.append(token.getLineNumber()); sourceLine.append(token.getLineNumber());
sourceLine.append(" "); sourceLine.append(" ");
} else if (token.isToken()) { } else if (token.isToken()) {
sourceLine.append(token.getTokenString()); sourceLine.append(token.getTokenString());
processToken(sourceAssembly, sourceLine, token, tokenizer);
} else if (token.isString()) { } else if (token.isString()) {
sourceLine.append(token.getStringValue()); sourceLine.append(token.getStringValue());
// FIXME - process string/expressions!
} }
return token;
} }
if (sourceLine.length() > 0) {
assemblyWriter.println(sourceLine.toString()); /**
assemblyWriter.println(sourceAssembly.toString()); * Take a peek at the next token.
*/
protected ApplesoftToken peekToken() {
if (tokenAlreadySeen == null) {
tokenAlreadySeen = tokenizer.getNextToken();
}
return tokenAlreadySeen;
}
/**
* Compile the given FileEntry and return the assembly code.
*/
public byte[] compile() throws CompileException {
StringBuffer programCode = new StringBuffer();
while (hasMoreTokens()) {
sourceLine.setLength(0); sourceLine.setLength(0);
sourceAssembly.setLength(0); sourceAssembly.setLength(0);
ApplesoftToken token = nextToken();
if (!token.isLineNumber()) {
throw new CompileException("Expecting a line number.");
}
sourceAssembly.append("LINE");
sourceAssembly.append(token.getLineNumber());
sourceAssembly.append(":\n");
do {
evaluateCommand();
token = peekToken();
if (token != null && token.isCommandSeparator()) {
token = nextToken();
}
} while (token != null && token.isCommandSeparator());
programCode.append(sourceLine);
programCode.append("\n");
programCode.append(sourceAssembly);
programCode.append("\n");
}
programCode.insert(0, buildUsedAddresses());
return programCode.toString().getBytes();
} }
if (!stringConstants.isEmpty()) { protected StringBuffer buildUsedAddresses() {
assemblyWriter.println("\n* String constant values:"); StringBuffer buf = new StringBuffer();
Iterator iterator = stringConstants.keySet().iterator(); if (usedAddresses.size() > 0) {
while (iterator.hasNext()) { buf.append("* Addresses:\n");
String name = (String) iterator.next(); for (int i=0; i<usedAddresses.size(); i++) {
assemblyWriter.print(name); String label = (String) usedAddresses.get(i);
assemblyWriter.print(":\tASC "); buf.append(label);
assemblyWriter.println(stringConstants.get(name)); buf.append(" = ");
assemblyWriter.println("\tHEX 00"); buf.append((String) knownAddresses.get(label));
buf.append("\n");
} }
buf.append("\n");
} }
if (!integerConstants.isEmpty()) { return buf;
assemblyWriter.println("\n* Integer constant values:");
Iterator iterator = integerConstants.keySet().iterator();
while (iterator.hasNext()) {
String name = (String) iterator.next();
assemblyWriter.print(name);
assemblyWriter.print(":\tDW ");
assemblyWriter.println(integerConstants.get(name));
}
} }
if (!integerVariables.isEmpty()) { protected void evaluateCommand() {
assemblyWriter.println("\n* Integer variables:"); ApplesoftToken token = nextToken();
for (int i = 0; i<integerVariables.size(); i++) { while (token != null && token.isCommandSeparator()) {
assemblyWriter.print(integerVariables.get(i)); token = nextToken();
assemblyWriter.println(": DW 0");
} }
if (token == null || !token.isToken()) {
return; // end of line (no command on line...)
} }
if (!floatVariables.isEmpty()) { Method method = getMethod(token);
assemblyWriter.println("\n* Float variables:"); if (method != null) {
for (int i = 0; i<floatVariables.size(); i++) { try {
assemblyWriter.print(floatVariables.get(i)); method.invoke(this, new Object[0]);
assemblyWriter.println(": DS 5"); } catch (IllegalArgumentException e) {
} e.printStackTrace();
} } catch (IllegalAccessException e) {
e.printStackTrace();
assemblyWriter.close(); } catch (InvocationTargetException e) {
return assemblyStream.toByteArray(); e.printStackTrace();
}
/**
* Process and individual token.
*/
protected void processToken(StringBuffer sourceAssembly,
StringBuffer sourceLine, ApplesoftToken token, ApplesoftTokenizer tokenizer) {
String expr = null;
switch (token.getTokenValue()) {
case HOME: sourceAssembly.append("\tJSR $FC58\n");
break;
case STOP:
case RETURN:
case END: sourceAssembly.append("\tRTS\n");
break;
case TEXT: sourceAssembly.append("\tJSR $FB2F\n");
break;
case HGR: sourceAssembly.append("\tJSR $F3E2\n");
break;
case HGR2: sourceAssembly.append("\tJSR $F3D8\n");
break;
case GR: sourceAssembly.append("\tJSR $FB40\n");
break;
case INVERSE:
sourceAssembly.append("\tLDA #$3F\n");
sourceAssembly.append("\tSTA $32\n");
break;
case NORMAL:
sourceAssembly.append("\tLDA #$FF\n");
sourceAssembly.append("\tSTA $32\n");
break;
case FLASH:
sourceAssembly.append("\tLDA #$7F\n");
sourceAssembly.append("\tSTA $32\n");
break;
case VTAB: expr = evaluateExpression(sourceAssembly, sourceLine);
sourceAssembly.append("\tLDA ");
sourceAssembly.append(expr);
sourceAssembly.append("\n");
sourceAssembly.append("\tSTA $25\n");
sourceAssembly.append("\tJSR $FC66\n");
break;
case HTAB: expr = evaluateExpression(sourceAssembly, sourceLine);
sourceAssembly.append("\tLDA ");
sourceAssembly.append(expr);
sourceAssembly.append("\n");
sourceAssembly.append("\tSTA $24\n");
break;
case HCOLOR:
expr = evaluateExpression(sourceAssembly, sourceLine);
sourceAssembly.append("\tLDX ");
sourceAssembly.append(expr);
sourceAssembly.append("\n");
sourceAssembly.append("\tJSR $F6F0\n");
break;
case PRINT:
expr = evaluateExpression(sourceAssembly, sourceLine);
if (isIntegerVariable(expr)) {
throw new IllegalArgumentException("Integer not supported in print: " + expr);
} else if (isFloatVariable(expr)) {
sourceAssembly.append("\tLDY #>");
sourceAssembly.append(expr);
sourceAssembly.append("\n");
sourceAssembly.append("\tLDA #<");
sourceAssembly.append(expr);
sourceAssembly.append("\n");
sourceAssembly.append("\tJSR $EAF9\t; MOVFM\n");
sourceAssembly.append("\tJSR $ED2E\t; PRNTFAC\n");
} else if (isStringVariable(expr)) {
sourceAssembly.append("\tLDY #0\n");
sourceAssembly.append(":loop\tLDA ");
sourceAssembly.append(expr);
sourceAssembly.append(",Y\n");
sourceAssembly.append("\tBEQ :end\n");
sourceAssembly.append("\tJSR COUT\n");
sourceAssembly.append("\tINY\n");
sourceAssembly.append("\tBNE :loop\n");
sourceAssembly.append(":end\n");
} else {
throw new IllegalArgumentException("Invalid expr in print: " + expr);
}
break;
case FOR:
String loopVariable = evaluateExpression(sourceAssembly, sourceLine);
if (!isFloatVariable(loopVariable)) {
throw new IllegalArgumentException("FOR loop argument must be a float");
}
token = tokenizer.getNextToken();
if (token.getTokenValue() != EQUALS) {
throw new IllegalArgumentException("FOR requires =");
}
sourceLine.append(token.getTokenString());
String startValue = evaluateExpression(sourceAssembly, sourceLine);
token = tokenizer.getNextToken();
if (token.getTokenValue() != TO) {
throw new IllegalArgumentException("FOR requires TO");
}
sourceLine.append(token.getTokenString());
String endValue = evaluateExpression(sourceAssembly, sourceLine);
if (isFloatVariable(loopVariable)) {
// FIXME: Assumes start/end are integer
sourceAssembly.append("\tLDY ");
sourceAssembly.append(startValue);
sourceAssembly.append("\n");
sourceAssembly.append("\tLDA ");
sourceAssembly.append(startValue);
sourceAssembly.append("\n");
sourceAssembly.append("\tJSR $E2F2 ; GIVAYF\n");
sourceAssembly.append("\tLDY #>");
sourceAssembly.append(loopVariable);
sourceAssembly.append("\n");
sourceAssembly.append("\tLDX #<");
sourceAssembly.append(loopVariable);
sourceAssembly.append("\n");
sourceAssembly.append("\tJSR $EB2B ; MOVMF\n");
}
break;
}
}
/**
* Evaluate an expression and return the variable name that
* contains the value.
*/
protected String evaluateExpression(StringBuffer sourceAssembly, StringBuffer sourceLine) {
// FIXME: no type checking available
ApplesoftToken token = tokenizer.getNextToken();
if (token.isString()) {
String value = token.getStringValue();
sourceLine.append(value);
if (isIntegerNumber(value)) {
return addIntegerConstant(value);
} else if (value.startsWith("\"")) {
return addStringConstant(value);
} else { // assume variable name
return addVariable(value);
} }
} else { } else {
throw new IllegalArgumentException("Oops!"); // FIXME: Just during development - this should throw an exception!
while (peekToken() != null && !peekToken().isCommandSeparator() && !peekToken().isLineNumber()) {
nextToken();
}
} }
} }
/** protected Method getMethod(ApplesoftToken token) {
* Indicates if this string is a number. String tokenName = "evaluate" + token.getTokenString().trim();
*/ Method method = (Method) commandMethods.get(tokenName);
protected boolean isIntegerNumber(String value) { if (method == null) {
for (int i=0; i<value.length(); i++) { try {
if (!Character.isDigit(value.charAt(i))) { method = getClass().getMethod(tokenName, new Class[0]);
return false; commandMethods.put(tokenName, method);
} catch (SecurityException e) {
e.printStackTrace();
return null;
} catch (NoSuchMethodException e) {
// This is actually valid (during development anyway)
//e.printStackTrace();
return null;
} }
} }
return true; return method;
} }
protected String addIntegerConstant(String value) { protected void addAssembly(String label, String mnemonic, String parameter) {
String name = "INT" + value; if (label != null) {
if (!integerConstants.containsKey(name)) { sourceAssembly.append(label);
integerConstants.put(name, value); sourceAssembly.append(":\n");
}
if (mnemonic != null) {
sourceAssembly.append(" ");
sourceAssembly.append(mnemonic);
if (parameter != null) {
sourceAssembly.append(" ");
sourceAssembly.append(parameter);
if (!usedAddresses.contains(parameter)) {
usedAddresses.add(parameter);
}
}
sourceAssembly.append("\n");
} }
return name;
} }
protected String addStringConstant(String value) { public void evaluateHOME() {
String name = "STR" + stringConstants.size(); addAssembly(null, "JSR", "HOME");
if (stringConstants.containsValue(value)) {
Iterator iterator = stringConstants.keySet().iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
String keyValue = (String) stringConstants.get(key);
if (value.equals(keyValue)) {
name = key;
break;
}
}
} else {
stringConstants.put(name, value);
}
return name;
} }
protected String addVariable(String variableName) { public void evaluateTEXT() {
if (variableName.endsWith("$")) { addAssembly(null, "JSR", "TEXT");
variableName = "STR" + variableName;
if (!stringVariables.contains(variableName)) {
stringVariables.add(variableName);
}
} else if (variableName.endsWith("%")) {
variableName = "INT" + variableName;
if (!integerVariables.contains(variableName)) {
integerVariables.add(variableName);
}
} else {
variableName = "FP" + variableName;
if (!floatVariables.contains(variableName)) {
floatVariables.add(variableName);
}
}
return variableName;
} }
protected boolean isIntegerVariable(String name) { public void evaluateRETURN() {
return integerVariables.contains(name) || integerConstants.containsKey(name); addAssembly(null, "RTS", null);
} }
protected boolean isFloatVariable(String name) { public void evaluateEND() {
return floatVariables.contains(name) || floatConstants.containsKey(name); evaluateRETURN();
} }
protected boolean isStringVariable(String name) { public void evaluateHGR() {
return stringVariables.contains(name) || stringConstants.containsKey(name); addAssembly(null, "JSR", "HGR");
} }
public void evaluateHGR2() {
addAssembly(null, "JSR", "HGR2");
}
public void evaluateGR() {
addAssembly(null, "JSR", "GR");
}
public void evaluateINVERSE() {
addAssembly(null, "LDA", "#$3F");
addAssembly(null, "STA", "$32");
}
public void evaluateNORMAL() {
addAssembly(null, "LDA", "#$FF");
addAssembly(null, "STA", "$32");
}
public void evaluateFLASH() {
addAssembly(null, "LDA", "#$7F");
addAssembly(null, "STA", "$32");
}
// /**
// * Process and individual token.
// */
// protected void processToken(StringBuffer sourceAssembly,
// StringBuffer sourceLine, ApplesoftToken token, ApplesoftTokenizer tokenizer) {
// String expr = null;
// switch (token.getTokenValue()) {
// case VTAB: expr = evaluateExpression(sourceAssembly, sourceLine);
// sourceAssembly.append("\tLDA ");
// sourceAssembly.append(expr);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tSTA $25\n");
// sourceAssembly.append("\tJSR $FC66\n");
// break;
// case HTAB: expr = evaluateExpression(sourceAssembly, sourceLine);
// sourceAssembly.append("\tLDA ");
// sourceAssembly.append(expr);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tSTA $24\n");
// break;
// case HCOLOR:
// expr = evaluateExpression(sourceAssembly, sourceLine);
// sourceAssembly.append("\tLDX ");
// sourceAssembly.append(expr);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tJSR $F6F0\n");
// break;
// case PRINT:
// expr = evaluateExpression(sourceAssembly, sourceLine);
// if (isIntegerVariable(expr)) {
// throw new IllegalArgumentException("Integer not supported in print: " + expr);
// } else if (isFloatVariable(expr)) {
// sourceAssembly.append("\tLDY #>");
// sourceAssembly.append(expr);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tLDA #<");
// sourceAssembly.append(expr);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tJSR $EAF9\t; MOVFM\n");
// sourceAssembly.append("\tJSR $ED2E\t; PRNTFAC\n");
// } else if (isStringVariable(expr)) {
// sourceAssembly.append("\tLDY #0\n");
// sourceAssembly.append(":loop\tLDA ");
// sourceAssembly.append(expr);
// sourceAssembly.append(",Y\n");
// sourceAssembly.append("\tBEQ :end\n");
// sourceAssembly.append("\tJSR COUT\n");
// sourceAssembly.append("\tINY\n");
// sourceAssembly.append("\tBNE :loop\n");
// sourceAssembly.append(":end\n");
// } else {
// throw new IllegalArgumentException("Invalid expr in print: " + expr);
// }
// break;
// case FOR:
// String loopVariable = evaluateExpression(sourceAssembly, sourceLine);
// if (!isFloatVariable(loopVariable)) {
// throw new IllegalArgumentException("FOR loop argument must be a float");
// }
// token = tokenizer.getNextToken();
// if (token.getTokenValue() != EQUALS) {
// throw new IllegalArgumentException("FOR requires =");
// }
// sourceLine.append(token.getTokenString());
// String startValue = evaluateExpression(sourceAssembly, sourceLine);
// token = tokenizer.getNextToken();
// if (token.getTokenValue() != TO) {
// throw new IllegalArgumentException("FOR requires TO");
// }
// sourceLine.append(token.getTokenString());
// String endValue = evaluateExpression(sourceAssembly, sourceLine);
// if (isFloatVariable(loopVariable)) {
// // FIXME: Assumes start/end are integer
// sourceAssembly.append("\tLDY ");
// sourceAssembly.append(startValue);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tLDA ");
// sourceAssembly.append(startValue);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tJSR $E2F2 ; GIVAYF\n");
// sourceAssembly.append("\tLDY #>");
// sourceAssembly.append(loopVariable);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tLDX #<");
// sourceAssembly.append(loopVariable);
// sourceAssembly.append("\n");
// sourceAssembly.append("\tJSR $EB2B ; MOVMF\n");
// }
// break;
// }
// }
//
// /**
// * Evaluate an expression and return the variable name that
// * contains the value.
// */
// protected String evaluateExpression(StringBuffer sourceAssembly, StringBuffer sourceLine) {
// // FIXME: no type checking available
// ApplesoftToken token = tokenizer.getNextToken();
// if (token.isString()) {
// String value = token.getStringValue();
// sourceLine.append(value);
// if (isIntegerNumber(value)) {
// return addIntegerConstant(value);
// } else if (value.startsWith("\"")) {
// return addStringConstant(value);
// } else { // assume variable name
// return addVariable(value);
// }
// } else {
// throw new IllegalArgumentException("Oops!");
// }
// }
//
// /**
// * Indicates if this string is a number.
// */
// protected boolean isIntegerNumber(String value) {
// for (int i=0; i<value.length(); i++) {
// if (!Character.isDigit(value.charAt(i))) {
// return false;
// }
// }
// return true;
// }
//
// protected String addIntegerConstant(String value) {
// String name = "INT" + value;
// if (!integerConstants.containsKey(name)) {
// integerConstants.put(name, value);
// }
// return name;
// }
//
// protected String addStringConstant(String value) {
// String name = "STR" + stringConstants.size();
// if (stringConstants.containsValue(value)) {
// Iterator iterator = stringConstants.keySet().iterator();
// while (iterator.hasNext()) {
// String key = (String) iterator.next();
// String keyValue = (String) stringConstants.get(key);
// if (value.equals(keyValue)) {
// name = key;
// break;
// }
// }
// } else {
// stringConstants.put(name, value);
// }
// return name;
// }
//
// protected String addVariable(String variableName) {
// if (variableName.endsWith("$")) {
// variableName = "STR" + variableName;
// if (!stringVariables.contains(variableName)) {
// stringVariables.add(variableName);
// }
// } else if (variableName.endsWith("%")) {
// variableName = "INT" + variableName;
// if (!integerVariables.contains(variableName)) {
// integerVariables.add(variableName);
// }
// } else {
// variableName = "FP" + variableName;
// if (!floatVariables.contains(variableName)) {
// floatVariables.add(variableName);
// }
// }
// return variableName;
// }
//
// protected boolean isIntegerVariable(String name) {
// return integerVariables.contains(name) || integerConstants.containsKey(name);
// }
//
// protected boolean isFloatVariable(String name) {
// return floatVariables.contains(name) || floatConstants.containsKey(name);
// }
//
// protected boolean isStringVariable(String name) {
// return stringVariables.contains(name) || stringConstants.containsKey(name);
// }
} }