diff --git a/src/com/webcodepro/applecommander/compiler/ApplesoftCompiler.java b/src/com/webcodepro/applecommander/compiler/ApplesoftCompiler.java index 62c41a2..e26f786 100644 --- a/src/com/webcodepro/applecommander/compiler/ApplesoftCompiler.java +++ b/src/com/webcodepro/applecommander/compiler/ApplesoftCompiler.java @@ -24,13 +24,16 @@ import com.webcodepro.applecommander.util.ApplesoftToken; import com.webcodepro.applecommander.util.ApplesoftTokenizer; import com.webcodepro.applecommander.util.ApplesoftTokens; -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Properties; /** * Compile the given file as an Applesoft file. @@ -40,13 +43,30 @@ import java.util.Map; */ public class ApplesoftCompiler implements ApplesoftTokens { private ApplesoftTokenizer tokenizer; - - private List stringVariables = new ArrayList(); - private List integerVariables = new ArrayList(); - private List floatVariables = new ArrayList(); - private Map stringConstants = new HashMap(); - private Map integerConstants = new HashMap(); - private Map floatConstants = new HashMap(); + private ApplesoftToken tokenAlreadySeen; + private StringBuffer sourceAssembly = new StringBuffer(); + private StringBuffer sourceLine = new StringBuffer(); + + /** + * Contains a list of all known Apple ROM addresses that may be used + * by the compiled program. This map is keyed by the name of the + * address and the value is the address. + */ + 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. @@ -54,296 +74,406 @@ public class ApplesoftCompiler implements ApplesoftTokens { public ApplesoftCompiler(FileEntry fileEntry) { super(); tokenizer = new ApplesoftTokenizer(fileEntry); + initializeKnownAddresses(); + } + + /** + * Load known memory addresses from AppleMemoryAddresses.properties. + */ + protected void initializeKnownAddresses() { + InputStream inputStream = + getClass().getResourceAsStream("AppleMemoryAddresses.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + Enumeration keys = properties.keys(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + knownAddresses.put(key, properties.getProperty(key)); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + /** + * 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(token.getLineNumber()); + sourceLine.append(" "); + } else if (token.isToken()) { + sourceLine.append(token.getTokenString()); + } else if (token.isString()) { + sourceLine.append(token.getStringValue()); + } + return token; + } + + /** + * 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() { - ByteArrayOutputStream assemblyStream = new ByteArrayOutputStream(); - PrintWriter assemblyWriter = new PrintWriter(assemblyStream); - StringBuffer sourceLine = new StringBuffer(); - StringBuffer sourceAssembly = new StringBuffer(); - while (tokenizer.hasMoreTokens()) { - ApplesoftToken token = tokenizer.getNextToken(); - if (token == null) { - break; - } else if (token.isLineNumber()) { - if (sourceLine.length() > 0) { - assemblyWriter.println(sourceLine.toString()); - assemblyWriter.println(sourceAssembly.toString()); - sourceLine.setLength(0); - sourceAssembly.setLength(0); - } - sourceAssembly.append("LINE"); - sourceAssembly.append(token.getLineNumber()); - sourceAssembly.append(":\n"); - sourceLine.append("* "); - sourceLine.append(token.getLineNumber()); - sourceLine.append(" "); - } else if (token.isToken()) { - sourceLine.append(token.getTokenString()); - processToken(sourceAssembly, sourceLine, token, tokenizer); - } else if (token.isString()) { - sourceLine.append(token.getStringValue()); - // FIXME - process string/expressions! - } - } - if (sourceLine.length() > 0) { - assemblyWriter.println(sourceLine.toString()); - assemblyWriter.println(sourceAssembly.toString()); + public byte[] compile() throws CompileException { + StringBuffer programCode = new StringBuffer(); + while (hasMoreTokens()) { sourceLine.setLength(0); sourceAssembly.setLength(0); - } - - if (!stringConstants.isEmpty()) { - assemblyWriter.println("\n* String constant values:"); - Iterator iterator = stringConstants.keySet().iterator(); - while (iterator.hasNext()) { - String name = (String) iterator.next(); - assemblyWriter.print(name); - assemblyWriter.print(":\tASC "); - assemblyWriter.println(stringConstants.get(name)); - assemblyWriter.println("\tHEX 00"); + 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"); } - if (!integerConstants.isEmpty()) { - 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()) { - assemblyWriter.println("\n* Integer variables:"); - for (int i = 0; i"); - 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; + protected StringBuffer buildUsedAddresses() { + StringBuffer buf = new StringBuffer(); + if (usedAddresses.size() > 0) { + buf.append("* Addresses:\n"); + for (int i=0; i"); +// 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