package com.dreamfabric.c64utils; import java.util.Hashtable; import java.util.Vector; import com.dreamfabric.jac64.*; import java.io.InputStream; /** * A simple assembler for JaC64 (C64) / 6502 * Joakim Eriksson, JaC64.com / Dreamfabric.com * ---------------------------------------------- * Should be almost compatible (?) with a65 and * some other C64 assemblers * ---------------------------------------------- * To get full functionality for loading binaries * you need to override openBinary! * * Created: Fri Dec 29 09:52:38 2006 * * @author Joakim Eriksson * @version 1.0 */ public class Assembler { public static final int DEBUG_LEVEL = 0; public static final int CODE = 1; public static final int COMMENT = 2; public static final int REF_WORD = 0; public static final int REF_BYTE = 1; public static final int REF_BYTE_LO = 2; public static final int REF_BYTE_HI = 3; public static final int REF_RELATIVE = 4; public static final int[] REF_SIZE = new int[] { 2,1,1,1,1 }; private int mode = CODE; // pos => address private int pos = 0; private int lastPos = 0; private Hashtable labels = new Hashtable(); private Vector references = new Vector(); private String currentLine; private String[] tokens; private int[] memory = new int[0x10000]; private int lineNo; protected String workingDir; /** * Creates a new <code>Assembler</code> instance. * */ public Assembler() { } public void setMemory(int[] memory) { this.memory = memory; } public void setWorkingDir(String dir) { workingDir = dir; if (!workingDir.endsWith("/")) { workingDir += "/"; } System.out.println("Set working dir to " + workingDir); } public int[] assemble(String s, int start) { String[] lines = split(s, "\n", false); labels.clear(); references.removeAllElements(); setPos(start); mode = CODE; // First pass... for (int i = 0, n = lines.length; i < n; i++) { currentLine = lines[i]; if (assembleLine(lines[i], memory, pos) == 1) break; lineNo++; } resolve(); return memory; } private void setPos(int start) { pos = start; lastPos = pos; System.out.println("Location set to: " + start); } private int assembleLine(String lineOrig, int[] memory, int adr) { String line = lineOrig; if (DEBUG_LEVEL > 1) System.out.println("Assembling line: '" + line + "' at " + hex4(pos)); if (mode == COMMENT) { if (line.endsWith("*/")) mode = CODE; return 0; } if (line.startsWith("/*")) { mode = COMMENT; return 0; } tokens = split(line, " \t", true); if (tokens == null || tokens.length == 0) return 0; String label = tokens[0].trim(); if ((tokens.length == 0) || ((tokens.length == 1) && (label.length() == 0))) return 0; if (label.equals(".end")) { if (DEBUG_LEVEL > 0) { System.out.println("*** Assembly file ended (.end) "); System.out.println("-------------------------------"); System.out.println("Total code size: " + currentLine + " lines"); System.out.println("Ending position: " + hex4(pos)); System.out.println("-------------------------------"); } return 1; } String op = null; String operand = null; if (tokens.length > 1) op = tokens[1]; if (tokens.length > 2) operand = tokens[2]; if (label.length() > 0) { // Add label can "consume" this line if (addLabel(label, op, operand)) return 0; } // System.out.println("OP:" + op + " Operand: " + operand); char c = op.charAt(0); if (c == '.') { if (op.equals(".word")) { pos += setValue(REF_WORD, pos, operand, true); } else if (op.equals(".byte")) { pos += setValue(REF_BYTE, pos, operand, true); } else if (op.equals(".org")) { // label free... setPos(parseInt(operand)); } else if (op.equals(".align")) { // label free... int al = parseInt(operand); System.out.println("Aligning to " + al); setPos(pos - (pos & (al - 1)) + al); } else if (op.equals(".binary")) { // label free... loadBinary(operand); } else if (op.equals(".wdir")) { // label free... setWorkingDir(operand); } else { error("unhandled operation '" + op + "'"); } } else if (op.equals("*=")) { // label free... setPos(resolve(operand, pos)); } else if (op.startsWith(";")) { // Ignore return 0; } else { // Here is an op? int opI = MOS6510Ops.lookup(op); // System.out.println("### OP:" + opI + " " + line); switch (opI) { case MOS6510Ops.BNE: case MOS6510Ops.BEQ: case MOS6510Ops.BCC: case MOS6510Ops.BCS: case MOS6510Ops.BVC: case MOS6510Ops.BPL: case MOS6510Ops.BMI: setBranch(MOS6510Ops.lookup(opI, MOS6510Ops.RELATIVE), operand); break; // ops case MOS6510Ops.PLA: case MOS6510Ops.PLP: case MOS6510Ops.PHA: case MOS6510Ops.PHP: case MOS6510Ops.TAY: case MOS6510Ops.TAX: case MOS6510Ops.TYA: case MOS6510Ops.TXA: case MOS6510Ops.TXS: case MOS6510Ops.TSX: case MOS6510Ops.RTS: case MOS6510Ops.RTI: case MOS6510Ops.SEI: case MOS6510Ops.CLI: case MOS6510Ops.CLC: case MOS6510Ops.CLD: case MOS6510Ops.CLV: case MOS6510Ops.SEC: case MOS6510Ops.SED: case MOS6510Ops.INX: case MOS6510Ops.INY: case MOS6510Ops.DEX: case MOS6510Ops.DEY: case MOS6510Ops.BRK: case MOS6510Ops.NOP: memory[pos++] = MOS6510Ops.lookup(opI, 0); //System.out.println("#### Single OP: " + MOS6510Ops.lookup(opI, 0)); break; case MOS6510Ops.JMP: case MOS6510Ops.LDA: case MOS6510Ops.STA: case MOS6510Ops.LDX: case MOS6510Ops.STX: case MOS6510Ops.LDY: case MOS6510Ops.STY: case MOS6510Ops.EOR: case MOS6510Ops.ORA: case MOS6510Ops.AND: case MOS6510Ops.INC: case MOS6510Ops.DEC: case MOS6510Ops.CMP: case MOS6510Ops.CPY: case MOS6510Ops.CPX: case MOS6510Ops.ROL: case MOS6510Ops.ROR: case MOS6510Ops.LSR: case MOS6510Ops.ASL: case MOS6510Ops.ADC: case MOS6510Ops.SBC: case MOS6510Ops.BIT: c = operand.charAt(0); if (c == '#') { // Immediate! setOP(opI, MOS6510Ops.IMMEDIATE, pos++); pos += setValue(REF_BYTE, pos, operand.substring(1)); } else if (c == '(') { String oplow = operand.toLowerCase(); int adrMode = MOS6510Ops.INDIRECT; int size = REF_WORD; if (oplow.endsWith(")")) { operand = operand.substring(1, operand.length() - 1); } else if (oplow.endsWith(",x)")) { adrMode = MOS6510Ops.INDIRECT_X; size = REF_BYTE; operand = operand.substring(1, operand.length() - 3); } else if (oplow.endsWith("),y")) { adrMode = MOS6510Ops.INDIRECT_Y; size = REF_BYTE; operand = operand.substring(1, operand.length() - 3); } else error("Illegal syntax on indirection op " + op); setOP(opI, adrMode, pos++); pos += setValue(size, pos, operand); } else { String oplow = operand.toLowerCase(); int adrMode = MOS6510Ops.ABSOLUTE; int adrModeZ = MOS6510Ops.ZERO; int size = REF_WORD; if (oplow.endsWith(",x")) { operand = operand.substring(0, operand.length() - 2); adrMode = MOS6510Ops.ABSOLUTE_X; adrModeZ = MOS6510Ops.ZERO_X; } else if (oplow.endsWith(",y")) { operand = operand.substring(0, operand.length() - 2); adrMode = MOS6510Ops.ABSOLUTE_Y; adrModeZ = MOS6510Ops.ZERO_Y; } if (byteSize(operand)) { size = REF_BYTE; adrMode = adrModeZ; } setOP(opI, adrMode, pos++); pos += setValue(size, pos, operand); } break; case MOS6510Ops.JSR: setOP(opI, 0, pos++); pos += setValue(REF_WORD, pos, operand); break; default: error("Unhandled OP: '" + op + "' at " + hex4(pos)); } } if (DEBUG_LEVEL > 1) { System.out.print("Assembled line " + lineNo + ": " + lineOrig + " => "); for (int i = lastPos, n = pos; i < n; i++) { if (i > lastPos) System.out.print(", "); System.out.print(hex2(memory[i])); } System.out.println(); } lastPos = pos; return 0; } // reads a string constand from the input string // FIX THIS!!! - is not 100% good since some op's can be made on strings... // see A65.txt... private int handleStringConstant(int pos, String operand) { char c = operand.charAt(0); int num = 0; if (c == '"') { // What is the stuffing method here? for (int i = 1, n = operand.length(); i < n; i++) { c = operand.charAt(i); if (c != '"') { memory[pos] = c; pos++; num++; } } } else if (c == '\'') { boolean stuffed = false; for (int i = 1, n = operand.length(); i < n; i++) { c = operand.charAt(i); if (c != '\'' || stuffed) { memory[pos] = c; pos++; num++; stuffed = false; } else { stuffed = true; } } } return num; } public InputStream openBinary(String binary) { return null; // return new FileInputStream(binary); } private void loadBinary(String binary) { try { if (workingDir == null) workingDir = ""; InputStream fs = openBinary(workingDir + binary); if (fs == null) return; int data = 0; int initPos = pos; while ((data = fs.read()) != -1) { memory[pos++] = data & 0xff; } System.out.println("Loaded binary file at: " + hex4(initPos) + " len: " + (pos - initPos)); fs.close(); } catch (Exception e) { error("Could not load binary file " + binary); } } private void setOP(int opI, int mode, int pos) { int opR = MOS6510Ops.lookup(opI, mode); if (opR == -1) error(MOS6510Ops.modeString(mode) + " mode not available for " + MOS6510Ops.INS_STR[opI]); memory[pos] = opR; } // This could handle arrays also!!! - and therefore also increase pos... private int setValue(int type, int pos, String value) { return setValue(type, pos, value, false); } private int setValue(int type, int pos, String value, boolean allowArrays) { if (allowArrays) { char c = value.charAt(0); if (c == '\'' || c == '"') { int lp = currentLine.indexOf(c); return handleStringConstant(pos, currentLine.substring(lp)); } else { // handle ordinary array... // Can either be just picked from tokens or from something else... // The normal value is from tokens[2], here we can either have // multiple more tokens, or possibly one token with , as separator if (tokens.length > 3) { return setValueArr(type, pos, tokens, 2); } else if (value.indexOf(',') > 0) { String[] tok2 = split(value, ",", true); return setValueArr(type, pos, tok2, 0); } else { if (DEBUG_LEVEL > 0) System.out.println("Array? : value = " + value); } } } int val = parseInt(value); if (val != -1) { setValue(type, pos, val); return REF_SIZE[type]; } else { // Label createRef(type, pos, value); return REF_SIZE[type]; } } private int setValueArr(int type, int pos, String[] tokens, int start) { int len = 0; System.out.println("*** Possible array!!!"); for (int i = start, n = tokens.length; i < n; i++) { if (tokens[i].startsWith(";")) return len; if (tokens[i].indexOf(',') >= 0) { len += setValueArr(type, pos + len, split(tokens[i], ",", true), 0); } else { len += setValue(type, pos + len, tokens[i], false); System.out.println("Arr[" + (i - start) + "] = " + tokens[i]); } } return len; } private void setValue(int type, int pos, int value) { switch (type) { case REF_WORD: setWordValue(pos, value); break; case REF_BYTE_HI: setByteValue(pos, value >> 8); break; case REF_BYTE_LO: case REF_BYTE: setByteValue(pos, value & 0xff); break; case REF_RELATIVE: setRelValue(pos, value); break; } } private void error(String s) { throw new IllegalArgumentException(s + " at line " + lineNo); } private void resolve() { if (DEBUG_LEVEL > 0) { System.out.println("Resolving.... " + references.size()); } for (int i = 0, n = references.size(); i < n; i += 2) { String name = (String) references.elementAt(i); int[] data = (int[]) references.elementAt(i + 1); int type = data[0]; int pos = data[1]; if (DEBUG_LEVEL > 0) { System.out.print("Resolving " + name); } int val = resolve(name, pos); if (DEBUG_LEVEL > 0) { System.out.println(" to " + hex4(val) + " at pos:" + hex4(pos)); } setValue(type, pos, val); } } private int resolve(String name, int pos) { char c = name.charAt(0); if (c == '<') { return resolve(name.substring(1), pos) & 0xff; } else if (c == '>') { return resolve(name.substring(1), pos) >> 8; } // split on "special" chars (+-/*, etc) for (int i = 0, n = name.length(); i < n; i++) { c = name.charAt(i); if (c == '+') { return resolve(name.substring(0, i), pos) + resolve(name.substring(i + 1), pos); } else if (c == '-') { return resolve(name.substring(0, i), pos) - resolve(name.substring(i + 1), pos); } else if (c == '/') { return resolve(name.substring(0, i), pos) / resolve(name.substring(i + 1), pos); } else if (c == '*') { if (i == 0) { // return the position - 1 since op started at -1 if (n == 1) return (pos - 1); // if not just a star => it is an expression... let it // but cut i pieces... } else { return resolve(name.substring(0, i), pos) * resolve(name.substring(i + 1), pos); } } } int adr = getLabelAddress(name); if (adr == -1) { // Here we should try parsing as an integer also... int val = parseInt(name); if (val == -1) error("### Could not find label " + name); return val; } return adr; } private void setBranch(int op, String line) { memory[pos++] = op; setValue(REF_RELATIVE, pos, line); pos++; } private boolean byteSize(String s) { int x = parseInt(s); if (x != -1 && x < 0x100) return true; // Should also check labels, etc... return false; } public static int parseInt(String s) { char c = s.charAt(0); if (s.endsWith(",")) { // Formulated as x, y, z, ... s = s.substring(0, s.length() - 1); System.out.println("Ends with , => " + s); } int val = -1; try { if (c == '$') { s = s.substring(1); val = Integer.parseInt(s, 16); } else if (c == '%') { s = s.substring(1); val = Integer.parseInt(s, 2); } else if (c == '@') { s = s.substring(1); val = Integer.parseInt(s, 8); } else { val = Integer.parseInt(s); } } catch (Exception e) { } return val; } private void setWordValue(int pos, int val) { memory[pos] = val & 0xff; memory[pos + 1] = (val >> 8) & 0xff; } private void setByteValue(int pos, int val) { memory[pos] = val & 0xff; } // Current address (pos) and target address // val - pos => branch positive values is forward // Note: pos needs to be at position after branch op when // calculating this! private void setRelValue(int pos, int val) { // Branch value! memory[pos] = (val - (pos + 1)) & 0xff; if (DEBUG_LEVEL > 0) { System.out.println("Set branch value of " + memory[pos] + " at " + hex4(pos)); } } private void createRef(int type, int pos, String s) { s = s.toLowerCase().trim(); if (DEBUG_LEVEL > 0) { System.out.println("*** Creating reference to: '" + s + "' at " + hex4(pos)); } // The labelname + values! references.addElement(s); references.addElement(new int[] {type, pos}); } // returns true if this line does not need more handling... private boolean addLabel(String label, String op, String operand) { if (label.startsWith(";")) return true; boolean rval = false; if (op == null || (op != null && op.startsWith(";"))) { rval = true; } if ("*".equals(label)) { if ("=".equals(op)) { setPos(parseInt(operand)); System.out.println("*** New location: " + hex4(pos)); } return true; } else { int[] lpos = new int[1]; if ("=".equals(op) || ".equ".equals(op)) { lpos[0] = parseInt(operand); rval = true; } else { // special treatment of .org since it needs to change pos // before setting the label! if (".org".equals(op)) { setPos(parseInt(operand)); rval = true; } lpos[0] = pos; } labels.put(label, lpos); if (DEBUG_LEVEL > 0) { System.out.println("*** Added label: '" + label + "' = " + hex4(lpos[0])); } return rval; } } public int getLabelAddress(String label) { int[] lpos; lpos = (int[]) labels.get(label); if (lpos != null) { return lpos[0]; } return -1; } public void setByteValue(String label, int val) { System.out.println("Setting byte value of " + label + " to " + Integer.toString(val, 16)); int a = getLabelAddress(label); if (a != -1) { setByteValue(a, val); } else throw new IllegalArgumentException("Can not find label: " + label); } public void setWordValue(String label, int val) { System.out.println("Setting word value of " + label + " to " + Integer.toString(val, 16)); int a = getLabelAddress(label); if (a != -1) { setWordValue(a, val); } else throw new IllegalArgumentException("Can not find label: " + label); } private String hex4(int pos) { String s = null; if (pos < 0x10) s = "$000"; else if (pos < 0x100) s = "$00"; else if (pos < 0x1000) s = "$0"; else s = "$"; return s + Integer.toString(pos, 16); } private String hex2(int pos) { String s = null; if (pos < 0x10) s = "$0"; else s = "$"; return s + Integer.toString(pos, 16); } // Splits on chars in the splits string. private static String[] split(String data, String splits, boolean trim) { // System.out.println("Split called: '" + data + "' <?> " + splits); Vector strings = new Vector(); int lastPos = 0; int mode = 0; for (int i = 0, n = data.length(); i < n; i++) { char c = data.charAt(i); if (splits.indexOf(c) != -1) { if (mode == 0) { if (lastPos != 0 && ((lastPos + 1) < n)) lastPos++; strings.addElement(data.substring(lastPos, i)); } lastPos = i; mode = 1; } else { mode = 0; } } if (lastPos != 0 && lastPos + 1 < data.length()) lastPos++; strings.addElement(data.substring(lastPos, data.length())); String[] retval = new String[strings.size()]; for (int i = 0, n = retval.length; i < n; i++) { retval[i] = (String) strings.elementAt(i); } return retval; } }