mirror of
https://github.com/RevCurtisP/C02.git
synced 2024-11-24 15:31:17 +00:00
966 lines
31 KiB
C
966 lines
31 KiB
C
/* 6502 Assembler for C02 Compiler *
|
|
* Uses DASM Syntax but supports *
|
|
* 65C02 and Sweet 16 Opcodes */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include "a02.h"
|
|
|
|
#define DEBUG FALSE
|
|
int debug; //Ouput Debug Info
|
|
|
|
#define INCLEN 255
|
|
char incpath[INCLEN];
|
|
|
|
enum otypes {BINFIL, APLFIL, ATRFIL, INTFIL, MSRFIL, PRGFIL}; //Object File Types
|
|
int obin[] = {TRUE, FALSE, TRUE, FALSE, FALSE, TRUE}; //Is File Type Binary
|
|
int olen[] = {0, 8, 32, 0, 0}; //Hex Bytes per Line
|
|
|
|
char objtyp; //Object File Type
|
|
int objbin; //Binary Object File Flag
|
|
int objlen; //Text Object File Line Length
|
|
int objcnt; //Text Object File Byte Count
|
|
int chksum; //Text Object File Checksum
|
|
int orgadr; //Origin Address
|
|
int curadr; //Current Address
|
|
int endadr; //Address after Last Byte
|
|
int exeadr; //Execution Start Address
|
|
int lstadr; //List Address
|
|
|
|
char hdrtxt[MAXSTR]; //SREC Header Record Text
|
|
|
|
struct sym {int block; char name[MAXLBL+1]; int bytes, value, refrd;};
|
|
struct sym symbol; //Current Symbol
|
|
struct sym symtbl[MAXSYM]; //Global Symbol Table
|
|
int symcnt; //Number of Global Labels
|
|
int blkcnt; //Number of Block Labels Generated
|
|
int blknum; //Local Label Block Number (0 = Global)
|
|
|
|
char label[MAXSTR]; //Assembly Line Label
|
|
char mnmnc[MAXSTR]; //Opcode Mnemonic
|
|
char oprnd[MAXSTR]; //Operand Text
|
|
char cmmnt[MAXSTR]; //Assembly Line Comment
|
|
char mcode[MAXSTR]; //Generated Bytes
|
|
char strng[MAXSTR]; //Parsed String
|
|
|
|
|
|
int opridx; //Index into Operand
|
|
char cpdchr; //Last Character Copied into oprnd
|
|
|
|
unsigned char token, opmod; //OpCode Token, Modifier
|
|
unsigned int amode; //Addressing Modes
|
|
int zpage, opval; //ZeroPage Flag, Operand Value
|
|
|
|
char hexadr[6]; //Current Address in Hexadecimal
|
|
char bytstr[5]; //String Representation of Byte
|
|
|
|
char inplin[MAXSTR]; //Input Buffer
|
|
char *linptr; //Pointer into Input Buffer
|
|
int lineno; //Input File Line Number
|
|
int savlno; //Line Number (Saved)
|
|
|
|
int passno; //Assembler Pass Number (1 or 2)
|
|
int endasm; //End Assembly Flag
|
|
|
|
char prgnam[256]; //Assembler Path and Name (from Command Line)
|
|
char inpnam[256]; //Input File Name
|
|
char outnam[256]; //Output File Name
|
|
char lstnam[256]; //List File Name
|
|
char incnam[256]; //Include File Name
|
|
FILE *inpfil; //Input File Pointer
|
|
FILE *outfil; //Output File Pointer
|
|
FILE *lstfil; //List File Pointer
|
|
FILE *incfil; //Include File Pointer
|
|
|
|
/* Print Usage Info and Exit */
|
|
void usage(char* appnam) {
|
|
printf("Usage: %s [opts] asmfile objfile [lstfile]\n", appnam);
|
|
printf(" Opts: -d - Output debug info\n");
|
|
printf(" -h - SREC header record text\n");
|
|
printf(" -a - Apple-1 monitor format\n");
|
|
printf(" -p - Commodore PRG format\n");
|
|
printf(" -s - Motorola SREC format\n");
|
|
printf(" -x - Intel HEX format");
|
|
printf(" \n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Print Symbol Table */
|
|
void prtsym(void) {
|
|
fprintf(lstfil, "\n%s Symbol Table\nBlock Name Size Value Rfd\n", "Global");
|
|
for (int i=0; i<symcnt; i++) {
|
|
int refrd = (symtbl[i].refrd) ? '*' : ' ';
|
|
fprintf(lstfil, "%5d %-8s %4d %5d %c \n", symtbl[i].block, symtbl[i].name, symtbl[i].bytes, symtbl[i].value, refrd);
|
|
}
|
|
}
|
|
|
|
/* Print Error Dump Info */
|
|
void xdump(void) {
|
|
puts("Error Dump");
|
|
oprnd[opridx]=0; printf("Operand: %s\n", oprnd);
|
|
}
|
|
|
|
/* Print Error Message to Stream */
|
|
void xprint(FILE *fp, char* format, char *s) {
|
|
if (lineno) fprintf(fp, "%04d: ", lineno);
|
|
fprintf(fp, format, s);
|
|
}
|
|
|
|
/* Print Error Message and Exit */
|
|
void xerror(char* format, char *s) {
|
|
if (debug) xdump();
|
|
xprint(stderr, format, s);
|
|
if (lstfil) xprint(stderr, format, s);
|
|
if (lstfil && symcnt) prtsym();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Open File with Error Checking */
|
|
FILE * opnfil(char* name, char* mode, char* path) {
|
|
if (debug) printf("Opening file '%s' with mode '%s'\n", name, mode);
|
|
if (debug && strlen(path)) printf("using path list '%s'\n", path);
|
|
FILE *fp = fopen(name, mode);
|
|
if (!fp) {
|
|
char spec[256], paths[256];
|
|
strcpy(paths, path);
|
|
char *token = strtok(paths, " ");
|
|
while (token != NULL) {
|
|
strcpy(spec, token); strcat(spec, "\\"); strcat(spec, name);
|
|
if (debug) printf("Opening file \"%s\" with mode \"%s\"\n", spec, mode);
|
|
fp = fopen(spec, mode); if (fp) break;
|
|
token = strtok(NULL, " ");
|
|
if (debug) printf("token = \"%s\"\n", token);
|
|
}
|
|
}
|
|
if (!fp) xerror("Error Opening File '%s'\n", name);
|
|
if (debug) printf("File Opened\n");
|
|
return fp;
|
|
}
|
|
|
|
/* Open Object FIle */
|
|
FILE * opnobj(char* objnam, char* path) {
|
|
char mode[3] = "w";
|
|
objbin = obin[objtyp];
|
|
if (debug) printf("Set objbin to %d\n", objbin);
|
|
objlen = olen[objtyp];
|
|
if (debug) printf("Set objlen to %d\n", objlen);
|
|
if (objbin) strcat(mode, "b");
|
|
return opnfil(objnam, mode, path);
|
|
}
|
|
|
|
/* Skip Character in Input Line *
|
|
* Args: c - Character to Skip *
|
|
* Updates: linptr */
|
|
int skpchr(char c) {
|
|
if (*linptr == c) {linptr++; return TRUE;}
|
|
else return FALSE;
|
|
}
|
|
|
|
/* Skip Spaces in Input Line *
|
|
* Updates: linptr */
|
|
void skpspc(void) {
|
|
while (*linptr && *linptr <= ' ') linptr++;
|
|
}
|
|
|
|
/* Parse Word from Input Line *
|
|
* Args: skip - Skip Spaces Flag *
|
|
* *word - Buffer for Word *
|
|
* Updates: linptr *
|
|
* Returns: Word Found (TRUE/FALSE) */
|
|
int pword(int skip, char* word) {
|
|
int wrdlen = 0;
|
|
if (skip) skpspc();
|
|
while (isalnum(*linptr) || *linptr == '_') {
|
|
word[wrdlen++] = toupper(*linptr);
|
|
linptr++;
|
|
}
|
|
word[wrdlen] = 0; //Terminate String
|
|
if (wrdlen) return TRUE; else return FALSE;
|
|
}
|
|
|
|
struct sym *fndsym(int block, char* name) {
|
|
if (debug) printf("Searching for Symbol %s in block %d\n", name, block);
|
|
for (int i=0; i < symcnt; i++) {
|
|
if (symtbl[i].block != block || strcmp(symtbl[i].name,name)) continue;
|
|
if (debug) printf("Found Symbol %s, block %d, size %d, value %04x\n",
|
|
symtbl[i].name, symtbl[i].block, symtbl[i].bytes, symtbl[i].value);
|
|
return &symtbl[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Set Symbol Value and Size */
|
|
void setsym(int value, int bytes) {
|
|
symbol.value = value;
|
|
if (bytes) symbol.bytes = bytes;
|
|
else symbol.bytes = (value > 0xFF) ? 2 : 1;
|
|
symbol.refrd = FALSE;
|
|
if (debug) printf("Set Symbol %s in block %d to value %d, size %d, referred=False\n",
|
|
symbol.name, symbol.block, symbol.bytes, symbol.value);
|
|
}
|
|
|
|
/* Add Character to Beginning of String */
|
|
void pfxstr(char c, char* s) {
|
|
for (int i=strlen(s)+1; i; i--)
|
|
s[i] = s[i-1]; //Copy All Characters to the Right
|
|
s[0] = c; //Insert Character at Beginning
|
|
}
|
|
|
|
/* Parse Label from Input Line
|
|
* Sets: label
|
|
* Updates: linptr
|
|
* Returns: Label Found (TRUE/FALSE) */
|
|
int plabel(void) {
|
|
if (debug) puts("Parsing Label");
|
|
int block = (skpchr('.')) ? blknum : 0; //Local Label Block Number
|
|
int found = pword(FALSE, label); //Parse Word without Skipping Spaces
|
|
if (debug) {
|
|
if (found) printf("Found Label %s\n", label);
|
|
else puts("No Label Found");
|
|
}
|
|
skpchr(':'); //Skip Optional Label Terminator
|
|
if (found && passno == 1) {
|
|
if (label[0] && fndsym(block, label)) xerror("Duplicate Label %s Encountered\n", label);
|
|
if (debug) printf("Initializing Symbol %s\n", label);
|
|
symbol.block = block;
|
|
if (strlen(label) > MAXLBL) xerror("Label %s Too Long\n", label);
|
|
strcpy(symbol.name, label);
|
|
setsym(curadr, 0);
|
|
}
|
|
if (block) pfxstr('.', label);
|
|
skpspc(); //Skip to Mnemonic, Comment, or EOL
|
|
return found;
|
|
}
|
|
|
|
/* Append to Operand */
|
|
void aoprnd(int c) {
|
|
if (opridx < MAXSTR) oprnd[opridx++] = toupper(c);
|
|
}
|
|
|
|
/* Copy Character to Operand and Increment */
|
|
int cpychr(int c) {
|
|
if (c && toupper(*linptr) != c) return FALSE;
|
|
cpdchr = *linptr;
|
|
aoprnd(cpdchr);
|
|
linptr++;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Evaluate Binary Number */
|
|
int evlbin() {
|
|
int result = 0;
|
|
cpychr('%');
|
|
while (isdigit(*linptr)) {
|
|
if (*linptr > '1') break;
|
|
result = (result << 1) + *linptr - '0';
|
|
cpychr(0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Character Constant */
|
|
int evlchr() {
|
|
int result = 0;
|
|
cpychr('\'');
|
|
result = *linptr;
|
|
cpychr(0);
|
|
cpychr('\'');
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Decimal Number */
|
|
int evldec() {
|
|
int result = 0;
|
|
while (isdigit(*linptr)) {
|
|
result = result * 10 + *linptr - '0';
|
|
cpychr(0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Hexadecimal Number */
|
|
int evlhex() {
|
|
int result = 0;
|
|
cpychr('$');
|
|
while (isxdigit(*linptr)) {
|
|
int digit = toupper(*linptr) - '0';
|
|
if (digit > 9) digit = digit - 7;
|
|
result = (result << 4) + digit;
|
|
cpychr(0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Symbol */
|
|
struct sym *evlsym() {
|
|
char name[MAXSTR];
|
|
int block = (cpychr('.')) ? blknum : 0;
|
|
pword(TRUE, name);
|
|
for (int i=0; name[i]; i++) if (opridx<MAXSTR) oprnd[opridx++] = name[i];
|
|
struct sym *result = fndsym(block, name);
|
|
if (passno == 2 && result == NULL) xerror("Undefined Symbol %s\n", name);
|
|
if (result) result->refrd = TRUE; //Symbol was Referenced
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Term in Operand */
|
|
int evltrm() {
|
|
int result;
|
|
skpspc();
|
|
if (isalpha(*linptr) || *linptr == '.') {
|
|
struct sym *target = evlsym();
|
|
result = (target) ? target->value : 0x100;
|
|
}
|
|
else if (isdigit(*linptr))
|
|
result = evldec();
|
|
else switch(*linptr) {
|
|
case '$': result = evlhex(); break;
|
|
case '%': result = evlbin(); break;
|
|
case '\'': result = evlchr(); break;
|
|
default: result = -1;
|
|
}
|
|
skpspc();
|
|
if (debug) printf("Term Evaluated to %d\n", result);
|
|
return result;
|
|
}
|
|
|
|
/* Evaluate Operand */
|
|
int evlopd(int maxsiz) {
|
|
int result = 0;
|
|
int hilo = 0; //Return LSB (1) or MSB (2)
|
|
int prns; //Optional Parenthesis after Hi/Low Operator
|
|
if (debug) puts("Evaluating Operand");
|
|
skpspc();
|
|
if (cpychr('<')) hilo = 1;
|
|
else if (cpychr('>')) hilo = 2;
|
|
if (hilo) prns = cpychr('(');
|
|
result = evltrm();
|
|
if (result >= 0)
|
|
while (cpychr('+') || cpychr('-')) {
|
|
int opertr = cpdchr;
|
|
int opdval = evltrm();
|
|
if (opdval < 0) break;
|
|
if (opertr == '+') result += opdval;
|
|
else result -= opdval;
|
|
}
|
|
if (hilo) {
|
|
if (result < 0) xerror("Hi/Low Operator Requires Operand", "");
|
|
if (prns) cpychr(')'); //
|
|
switch (hilo) {
|
|
case 1: result = result & 0xFF; break; //LSB
|
|
case 2: result = result >> 8; break; //MSB
|
|
}
|
|
}
|
|
if (debug) printf("Operand Evaluated to %d\n", result);
|
|
if (result > maxsiz) xerror("Operand Value too Large\n", "");
|
|
return result;
|
|
}
|
|
|
|
/* Write Hex and Update Checksum */
|
|
void outhex(char prefix, int b) {
|
|
if (prefix) fputc(prefix, outfil);
|
|
fprintf(outfil, "%02X", b);
|
|
chksum += b;
|
|
} \
|
|
|
|
/* Write Hex Address */
|
|
void outadr(int w) {
|
|
outhex(0, w >> 8);
|
|
outhex(0, w & 0xFF);
|
|
}
|
|
|
|
/* Write Hex File Line Header */
|
|
void outhdr(int addr) {
|
|
chksum = 0;
|
|
int linlen = __min(objlen, endadr - curadr + 1);
|
|
if (debug) printf("outhdr: curadr=%04x, endadr=%04x, linlen=%04x\n", curadr, endadr, linlen);
|
|
switch (objtyp) {
|
|
case INTFIL: outhex(':', linlen); outadr(addr); outhex(0, 0); break;
|
|
default: outadr(addr); //Write Address
|
|
}
|
|
if (objtyp == APLFIL) fputc(':', outfil);
|
|
}
|
|
|
|
/* Write Checksum */
|
|
void outchk(void) {
|
|
if (objtyp == INTFIL) outhex(0, -chksum & 0xFF);
|
|
}
|
|
|
|
/* Write Hex File Line End */
|
|
void outend(void) {
|
|
if (objtyp = INTFIL) outchk(); //Write Checksum
|
|
fputs("\n", outfil);
|
|
objcnt = 0;
|
|
}
|
|
|
|
/* Write Hex File End of File */
|
|
void outeot(void) {
|
|
if (objcnt) outend();
|
|
if (objtyp = INTFIL); fputs(":00000001FF\n", outfil);
|
|
}
|
|
|
|
/* Write Byte to Hex Object File */
|
|
void outtxt(int b) {
|
|
if (objcnt == 0) outhdr(curadr - 1);
|
|
switch (objtyp) {
|
|
case APLFIL: outhex(' ', b); break;
|
|
default: outhex(0, b);
|
|
}
|
|
objcnt++;
|
|
if (objcnt == objlen) outend();
|
|
}
|
|
|
|
/* Write Byte to Output File */
|
|
void outbyt(int b) {
|
|
if (curadr > -1) curadr++;
|
|
if (passno != 2) return;
|
|
if (objbin) fputc(b & 0xFF, outfil);
|
|
else outtxt(b);
|
|
sprintf(bytstr, "%02X ", b);
|
|
if (strlen(mcode) < 9) strcat(mcode, bytstr);
|
|
}
|
|
|
|
/* Write Word to Output File */
|
|
void outwrd(int w) {
|
|
outbyt(w & 0xff);
|
|
outbyt(w >> 8);
|
|
}
|
|
|
|
/* Write Atari 8-Bit EXE Block Header */
|
|
void atrhdr(int saddr, int eaddr) {
|
|
outwrd(0xFFFF); //Binary File Indicator
|
|
outwrd(saddr); //Start Address
|
|
outwrd(eaddr-1); //End Address
|
|
}
|
|
|
|
/* Write Atari 8-Bit EXE Ending Block */
|
|
void atrend(void) {
|
|
atrhdr(0x02E0, 0x02E2);
|
|
outbyt(0x4C); //JMP
|
|
outwrd(exeadr); //start address
|
|
}
|
|
|
|
/* Lookup Opcode */
|
|
int lkpopc(struct opc opl[]) {
|
|
if (debug) printf("Looking up Mnemonic %s\n", mnmnc);
|
|
token = 0xFF; //Set Token to Invalid
|
|
char mne[5]; strncpy(mne, mnmnc, 4); mne[4] = 0; //Truncate Mnemonic to Four Characters
|
|
for (int i=0; opl[i].name[0]; i++) {
|
|
if (strcmp(opl[i].name, mne)) continue;
|
|
token = opl[i].token;
|
|
amode = opl[i].amode;
|
|
if (debug) printf("Found token %02X, amode %04X\n", token, amode);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Assemble BYTE Pseudo-Op */
|
|
void asmbyt(void) {
|
|
if (debug) puts("Assembling BYTE Pseudo-Op");
|
|
do {
|
|
if (cpychr('"')) { //String Operand
|
|
while (!cpychr('"')) {outbyt(*linptr); cpychr(0); }
|
|
skpspc();
|
|
} else
|
|
outbyt(evlopd(0xFF)); //Evaluate Operand
|
|
} while (cpychr(','));
|
|
}
|
|
|
|
/* Assemble HEX Pseudo-Op */
|
|
void asmhex(void) {
|
|
if (debug) puts("Assembling HEX Pseudo-Op");
|
|
do {outbyt(evlhex(0xFF)); } while (cpychr(','));
|
|
}
|
|
|
|
/* Assemble WORD Pseudo-Op */
|
|
void asmwrd(void) {
|
|
do {
|
|
outwrd(evlopd(0xFFFF)); //Evaluate Operand
|
|
} while (cpychr(','));
|
|
}
|
|
|
|
/* Assemble ALIGN Pseudo-Op */
|
|
void asmaln(void) {
|
|
if (debug) puts("Assembling ALIGN Pseudo-Op");
|
|
int size = evlopd(0xFFFF); if (size < 2) return;
|
|
if (debug) printf("Aligning to %d Bytes\n", size);
|
|
int fill = size - (curadr % size); if (fill == size) return;
|
|
if (debug) printf("Filling %d Bytes\n", fill);
|
|
for (int i=0; i<fill; i++) outbyt(0);
|
|
}
|
|
|
|
/* Assemble FILL Pseudo-Op */
|
|
void asmfll(void) {
|
|
if (debug) puts("Assembling FILL Pseudo-Op");
|
|
int size = evlopd(0xFFFF);
|
|
if (debug) printf("Filling %d Bytes\n", size);
|
|
for (int i=0; i<size; i++) outbyt(0);
|
|
}
|
|
|
|
/* Assemble EQU Pseudo-Op */
|
|
void asmequ(void) {
|
|
if (label[0] == 0) xerror("EQUate without Label", 0);
|
|
setsym(evlopd(0xFFFF), 0);
|
|
}
|
|
|
|
/* Assemble EXE Pseudo-Op */
|
|
void asmexe(void) {
|
|
exeadr = evlopd(0xFFFF);
|
|
}
|
|
|
|
/* Assemble ORG Pseudo-Op */
|
|
void asmorg(void) {
|
|
orgadr = evlopd(0xFFFF);
|
|
if (exeadr < 0) exeadr = orgadr;
|
|
if (passno == 1 && symbol.name[0]) {
|
|
symbol.value = orgadr;
|
|
symbol.bytes = 2;
|
|
}
|
|
if (passno == 2 ) {
|
|
if (objtyp == PRGFIL) outwrd(orgadr);
|
|
if (objtyp == ATRFIL) atrhdr(orgadr, lstadr);
|
|
}
|
|
curadr = orgadr;
|
|
lstadr = orgadr;
|
|
}
|
|
|
|
/* Assemble PROCESSOR Pseudo-Op */
|
|
void asmprc(void) {
|
|
skpspc();
|
|
while (isalnum(*linptr)) cpychr(0);
|
|
if (debug) printf("Ignoring Operand %s\n", oprnd);
|
|
}
|
|
|
|
/* Assemble SUBROUTINE Pseudo-Op */
|
|
void asmsub(void) {
|
|
blknum = ++blkcnt;
|
|
sprintf(oprnd, "%d", blknum); opridx = strlen(oprnd);
|
|
if (debug) printf("Block Number set to %s\n", oprnd);
|
|
}
|
|
|
|
/* Assemble ENDSUBROUTINE Pseudo-Op */
|
|
void asmens(void) {
|
|
blknum = 0;
|
|
sprintf(oprnd, "%d", blknum); opridx = strlen(oprnd);
|
|
if (debug) printf("Block Number reset to %s\n", oprnd);
|
|
}
|
|
|
|
/* Assemble INCLUDE Pseudo-Op */
|
|
void asminf(void) {
|
|
int incidx = 0;
|
|
if (debug) puts("Assembling INCLUDE Pseudo-Op");
|
|
if (incfil) xerror("Nested INCLUDE not Allowed", "");
|
|
if (!cpychr('"')) xerror("File Name Must be Quoted", "");
|
|
while (*linptr && !cpychr('"')) {
|
|
char c = *linptr; if (c == '/') c = '\\'; //Reverse Slashes for DOS/Windows
|
|
incnam[incidx++] = c;
|
|
cpychr(0);
|
|
}
|
|
incnam[incidx] = 0; //Terminate Include Name
|
|
if (incidx == 0) xerror("INCLUDE requires file name\n", "");
|
|
if (debug) printf("Include File Set to Name to '%s'\n", incnam);
|
|
}
|
|
|
|
/* Assemble END Pseudo-Op */
|
|
void asmend(void) {
|
|
endasm = TRUE;
|
|
}
|
|
|
|
/* Assemble Pseudo-Op */
|
|
int asmpso(int dot) {
|
|
if (lkpopc(psolst) == FALSE && dot == FALSE) return FALSE;
|
|
skpspc();
|
|
if (debug) printf("Assembling Pseudo-Op %s, Token '%c'\n", mnmnc, token);
|
|
switch (token) {
|
|
case '=': asmequ(); break; //EQU
|
|
case 'B': asmbyt(); break; //BYTE or DC
|
|
case 'H': asmhex(); break; //HEX
|
|
case 'W': asmwrd(); break; //WORD
|
|
case 'F': asmfll(); break; //FILL or DS
|
|
case 'S': asmsub(); break; //SUBRoutine
|
|
case 'M': asmens(); break; //ENDSubroutine
|
|
case 'I': asminf(); break; //INCLude
|
|
case '*': asmorg(); break; //ORG
|
|
case 'X': asmexe(); break; //EXEC
|
|
case 'P': asmprc(); break; //PROCessor
|
|
case 'E': asmend(); break; //END
|
|
case 'A': asmaln(); break; //ALIGn
|
|
default: xerror("Undefined Pseudo-Op %s\n", mnmnc);
|
|
}
|
|
if (dot) pfxstr('.', mnmnc);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Check for Valid Addressing Mode */
|
|
int chkmod(int mode) {
|
|
char* s = NULL; //Pointer to Addressing Mode Description
|
|
for (int i=0; amdesc[i].amode; i++)
|
|
if (amdesc[i].amode == mode) {s = amdesc[i].desc; break;}
|
|
if (debug) printf("Checking Addressing Mode %s, %04X against %04X\n", s, mode, amode);
|
|
if (mode & amode) return TRUE;
|
|
xerror("Invalid Addressing Mode %s", s);
|
|
}
|
|
|
|
/* Assemble Branch Opcode */
|
|
void asmbrn(int setzp) {
|
|
int offset = 0;
|
|
int ofsadj = (setzp) ? 2 : 3; //Offset Adjustment
|
|
if (debug) printf("Assembling Branch Opcode Token 0x%02X\n", token);
|
|
zpage = setzp;
|
|
if (isalpha(*linptr) || *linptr =='.') {
|
|
struct sym *target = evlsym();
|
|
if (target) offset = (target->value - curadr - ofsadj);
|
|
}
|
|
else if (cpychr('+')) offset = evlopd(0xFF);
|
|
else if (cpychr('-')) offset = -evlopd(0xFF);
|
|
else {
|
|
opval = evlopd(0xFFFF);
|
|
if (opval < 0) xerror("Illegal Branch Operand\n", "");
|
|
offset = opval - curadr - 2;
|
|
}
|
|
if (debug) printf("Calculated Branch Offset of %d\n", offset);
|
|
if ((offset > 127 || offset < -128) && passno == 2)
|
|
xerror("Branch Out of Range\n", "");
|
|
if (debug) printf("Branch Offset %d\n", offset);
|
|
opval = offset & 0xFF;
|
|
}
|
|
|
|
/* Assemble Zero Page, Relative Opcode */
|
|
void asmzpr(void) {
|
|
int bitno = -1;
|
|
if (debug) printf("Assembling ZeroPage (Relative) Opcode Token 0x%02X\n", token);
|
|
if (strlen(mnmnc) < 4) {opmod = evlopd(7) << 4; cpychr(','); skpspc();} //Set Modifier to Bit Position
|
|
int zpval = evlopd(0xFF); cpychr(','); skpspc();//Get ZeroPage Operand
|
|
if (zpval < 0) xerror ("Instruction %s requires Multiple Operands\n", mnmnc);
|
|
if (amode == 0x0004) {zpage = TRUE; opval = zpval;} //RMB, SMB - Zero Page Operand
|
|
else {asmbrn(FALSE); opval = opval << 8 | zpval;} //BBR, BBS - Combine Operanda
|
|
}
|
|
|
|
/* Assemble Immediate Mode Instruction */
|
|
void asmimd(void) {
|
|
if (debug) printf("Assembling Immediate Opcode Token 0x%02X\n", token);
|
|
opval = evlopd(0xFF);
|
|
zpage = TRUE;
|
|
opmod = 0x08; //Immediate
|
|
}
|
|
|
|
/* Assemble Indirect Mode Instruction */
|
|
void asmind(void) {
|
|
if (debug) puts("Assembling Indirect Mode Instruction");
|
|
zpage = TRUE; opval = evlopd(0xFFFF);
|
|
if (cpychr(',') && cpychr('X') && chkmod(INDCX)) cpychr(')'); ////(Indirect,X) opmod=0
|
|
else if (cpychr(')')) {
|
|
if (cpychr(',') && cpychr('Y') && chkmod(INDCY)) opmod = 0x10; //(Indirect),Y
|
|
else if (chkmod(INDCT)) opmod = 0x11; //(Indirect)
|
|
if (token == 0x4C) zpage = FALSE; //JMP (Indirect Absolute)
|
|
}
|
|
else chkmod(0); //Illegal Addressing Mode
|
|
if (zpage && opval > 0x00FF) xerror("Operand Value too Large\n", "");
|
|
}
|
|
|
|
/* Assemble Implied/Accumulator/Absolute/ZeroPage Mode Instruction */
|
|
void asmiaz(void) {
|
|
opval = evlopd(0xFFFF);
|
|
if (opval < 0) {
|
|
if (amode != IMPLD) //Implied
|
|
if (chkmod(ACMLT)) opmod = 0x08; //Accumulator
|
|
return;
|
|
}
|
|
if (debug) printf("Assembling Absolute/ZeroPage 0x%02X\n", token);
|
|
zpage = (opval <= 0xff) ? TRUE : FALSE;
|
|
if (zpage && chkmod(ZPAGE)) opmod = 0x04; //ZeroPage
|
|
else if (chkmod(ABSLT)) opmod = 0x0C; //Absolute
|
|
if (cpychr(',')) {
|
|
if (cpychr('X')) {
|
|
if (zpage && chkmod(ZPAGX)) opmod = 0x14; //ZeroPage,X
|
|
else if (chkmod(ABSLX)) opmod = 0x1C; //Absolute,X
|
|
} else if (cpychr('Y')) {
|
|
if (zpage && (token == 0x82 || token == 0xA2))
|
|
opmod = 0x14; //ZeroPage,Y
|
|
else {zpage = FALSE; opmod = 0x18;} //Absolute,Y
|
|
} else chkmod(0); //Illegal Addressing Mode
|
|
}
|
|
}
|
|
|
|
/* Fix Opcode (if needed) */
|
|
unsigned char fixopc(void) {
|
|
if (debug) printf("Fixing OpCode $%02X+$%02X\n", token, opmod);
|
|
for (int i=0; opfix[i].token; i++)
|
|
if (opfix[i].token == token && opfix[i].opmod == opmod)
|
|
return opfix[i].opcode;
|
|
return token + opmod;
|
|
}
|
|
|
|
/* Ouput Opcode debug Info */
|
|
void dbgopc(void) {
|
|
printf("token=$%02X, opmod=$%02X, Address Mode: ", token, opmod);
|
|
if (amode == 0x1004) puts("ZeroPage, Relative");
|
|
else if (amode == 0x0004) puts("ZeroPage");
|
|
else switch (opmod) {
|
|
case 0x00: if (amode == IMPLD) puts("Implied"); else puts("(Indirect,X)"); break;
|
|
case 0x08: if (opval < 0) puts("Accumulator"); else puts("#Immediate"); break;
|
|
case 0x10: puts("(Indirect),Y"); break;
|
|
case 0x11: puts("(Indirect)"); break;
|
|
case 0x04: puts("ZeroPage"); break;
|
|
case 0x0C: puts("Absolute"); break;
|
|
case 0x14: if ((token == 0x82 || token == 0xA2)) puts("ZeroPage,Y");
|
|
else puts("ZeroPage,X"); break;
|
|
case 0x1C: puts("Absolute,X"); break;
|
|
case 0x18: puts("Absolute,Y"); break;
|
|
default: puts("UNKOWN");
|
|
}
|
|
if (opval < 0) puts("No Operand");
|
|
else {
|
|
printf("Operand Value %d, ", opval);
|
|
if (zpage) puts("Zero Page"); else puts("Absolute");
|
|
}
|
|
}
|
|
|
|
/* Evaluate Register Operand *
|
|
* Returns: Register Number 0-15 */
|
|
int evlreg(void) {
|
|
char name[MAXSTR];
|
|
int result;
|
|
if (debug) puts("Evaluating Register Argument");
|
|
int rel = cpychr('@');
|
|
if (debug) printf("Set rel to %d\n", rel);
|
|
if (amode == INDCT && !rel) xerror("Indirect Argument Required\n", "");
|
|
if (amode == RGIND && rel) token = token + 0x20; //LD or ST relative
|
|
char pfx = 'R'; if (!cpychr(pfx)) pfx = 0;
|
|
if (debug) puts("Evaulating Register Number");
|
|
if (pfx && isalpha(*linptr)) return evlhex(0x0F);
|
|
if (isdigit(*linptr)) return evldec(0x0F);
|
|
if (!pword(FALSE, name)) xerror("Register Argument Required\n", "");
|
|
for (int i=0; name[i]; i++) if (opridx<MAXSTR) oprnd[opridx++] = name[i];
|
|
if (pfx) pfxstr(pfx, name);
|
|
if (debug) printf("Parsed Register Name %s \n", name);
|
|
if (strcmp(name, "ACC")==0) return 0; //Accumulator (R0)
|
|
if (strcmp(name, "PR")==0) return 14; //Prior Result (R14)
|
|
if (strcmp(name, "PC")==0) return 15; //Accumulator (R0)
|
|
xerror("Illegal Register %s\n", name);
|
|
}
|
|
|
|
/* Assemnble Register Instruction */
|
|
void asmreg(void){
|
|
if (debug) printf("Assembling Register Opcode %02x\n", token);
|
|
int regno = evlreg();
|
|
if (debug) printf("Register Number: %d\n", regno);
|
|
token = token | regno;
|
|
if (amode == ABSLT) {
|
|
if (debug) puts("Evaluating Absolure Argument");
|
|
skpspc();
|
|
if (!cpychr(',')) aoprnd(' '); //Skip optional comma
|
|
opval = evlopd(0xFFFF);
|
|
if (opval < 0) xerror("Absolute Argument Required\n", "");
|
|
}
|
|
}
|
|
|
|
/* Assemble Sweet 16 Opcode */
|
|
int asmswo(void) {
|
|
opval = -1;
|
|
if (lkpopc(swolst) == FALSE) return FALSE;
|
|
if (debug) printf("Assembling Sweet 16 Instruction %s, ", mnmnc);
|
|
if (debug) printf("Addressing Mode 0x%04X\n", amode);
|
|
skpspc();
|
|
switch (amode) {
|
|
case IMPLD: break; //Implied Instruction
|
|
case RELTV: asmbrn(TRUE); break; //Branch (Relative) Instruction
|
|
default: asmreg(); //Assemble Register Instruction
|
|
}
|
|
if (debug) printf("Writing OpCode $%02X\n", token);
|
|
outbyt(token);
|
|
if (debug) printf("Writing %s Operand %d\n", zpgabs[-zpage], opval);
|
|
if (opval >= 0) {if (amode == RELTV) outbyt(opval); else outwrd(opval);}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Assemble Opcode */
|
|
int asmopc(int dot) {
|
|
opmod = 0;
|
|
if (asmpso(dot)) return TRUE; //Check For/Assemble Pseudo-Op
|
|
if (asmswo()) return TRUE; //Check for/Assemble Sweet-16 Instruction
|
|
if (lkpopc(opclst) == FALSE) xerror("Invalid Mnemonic %s\n", mnmnc);
|
|
if (debug) printf("Assembling Opcode Token 0x%02X, ", token);
|
|
if (debug) printf("Addressing Mode Mask 0x%04X\n", amode);
|
|
skpspc();
|
|
if (amode == RELTV) asmbrn(TRUE); //Branch (Relative) Instruction
|
|
else if (amode == 0x0004 || amode == 0x1004) asmzpr(); //Branch ZP (Relative)
|
|
else if (cpychr('#')) asmimd(); //Assemble Immediate Instruction
|
|
else if (cpychr('(')) asmind(); //Assemble Indirect Instruction
|
|
else asmiaz(); //Assemble Implied/Accumulator/Absolute/ZeroPage Instruction
|
|
if (debug) dbgopc();
|
|
int opcode = fixopc();
|
|
if (debug) printf("Writing OpCode $%02X\n", opcode);
|
|
outbyt(opcode);
|
|
if (debug) printf("Writing %s Operand %d\n", zpgabs[-zpage], opval);
|
|
if (opval >= 0) {
|
|
if (zpage) outbyt(opval); //Byte Operand
|
|
else outwrd(opval); //Word Operand
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Parse Opcode Mnemonic from Input Line
|
|
* Sets: mnmnc
|
|
* Updates: linptr
|
|
* Returns: Label Found (TRUE/FALSE) */
|
|
int pmnmnc(void) {
|
|
if (debug) puts("Parsing Mnemonic");
|
|
int dot = cpychr('.'); //Optional Dot before Pseudo-Op
|
|
int found = pword(TRUE, mnmnc);
|
|
opridx = 0; //Initialize Operand Index
|
|
if (found) asmopc(dot);
|
|
oprnd[opridx] = 0; //Terminate Operand String
|
|
return found;
|
|
}
|
|
|
|
/* Parse Comment from Input Line
|
|
* Sets: cmmnt
|
|
* Updates: linptr */
|
|
void pcmmnt(void) {
|
|
skpspc();
|
|
int i = 0;
|
|
while (*linptr >= ' ') cmmnt[i++] = *linptr++;
|
|
cmmnt[i] = 0; //Terminate Comment
|
|
if (debug) {if (i) printf("Comment: %s\n", cmmnt); else puts("No Comment Found");}
|
|
}
|
|
|
|
/* Add Label to Symbol Table */
|
|
void addsym() {
|
|
if (symbol.value<0) xerror("Origin Not Set", "");
|
|
memcpy(&symtbl[symcnt++], &symbol, sizeof(symbol));
|
|
if (debug) printf("Added symbol %s in block %d to Symbol Table\n", symbol.name, symbol.block);
|
|
}
|
|
|
|
|
|
/* Open Include File */
|
|
void opninc(void) {
|
|
if (debug) printf("Opening Include File %s\n", incnam);
|
|
if (lstfil) fputs("\n", lstfil);
|
|
incfil = opnfil(incnam, "r", incpath);
|
|
savlno = lineno;
|
|
lineno = 1;
|
|
}
|
|
|
|
/* Close Include File */
|
|
void clsinc(void) {
|
|
if (debug) printf("Closing Include File %s\n", incnam);
|
|
if (lstfil) fputs("\n", lstfil);
|
|
fclose(incfil);
|
|
incfil = NULL;
|
|
incnam[0] = 0;
|
|
lineno = savlno;
|
|
endasm = FALSE; //Clear End Flag for Return to Main File
|
|
}
|
|
|
|
/* Close Object File */
|
|
void clsobj() {
|
|
if (objbin) {if (objtyp == ATRFIL) atrend();}
|
|
else {if (objcnt) outeot();}
|
|
fclose(outfil);
|
|
}
|
|
|
|
/* Assemble Input File (Two Pass) *
|
|
* Args: pass - Assembly Pass (1 or 2) *
|
|
* Requires: inpfil - Input File Pointer *
|
|
* Uses: inplin - Input Line Buffer *
|
|
* lineno - Input File Line Number */
|
|
void asmfil(int pass) {
|
|
endasm = FALSE; //Reset End Assembly Flag
|
|
passno = pass; //Assembly Pass Number
|
|
if (debug) printf("Assembling Pass %d\n", pass);
|
|
lineno = 1; //Initialize Input File Line Number
|
|
blkcnt = 0; //Initialize Local Block Count
|
|
blknum = 0; // and Local Block Number
|
|
orgadr = -1; //Origin Address Not Set
|
|
curadr = orgadr; //Set Current Address to Origin
|
|
exeadr = -1; //Execution Address Not Set
|
|
objcnt = 0; //Initialize Object File Byte Count
|
|
if (debug) printf("Rewinding Input File\n");
|
|
rewind(inpfil); //Start at Beginning of Input File
|
|
while (TRUE) {
|
|
if (incfil) linptr = fgets(inplin, MAXSTR, incfil);
|
|
else linptr = fgets(inplin, MAXSTR, inpfil);
|
|
if (endasm || linptr == NULL) {if (incfil) {clsinc(); continue;} else break;}
|
|
if (debug) printf("%1d %05d %04X: %s", passno, lineno, curadr, inplin);
|
|
lstadr = curadr; //Set List Address
|
|
mcode[0] = 0; //Clear Generated Macbine Code
|
|
plabel(); //Parse Label
|
|
pmnmnc(); //Parse Mnemonic
|
|
pcmmnt(); //Parse Comment
|
|
if (passno == 1 && label[0]) addsym(); //Add Label to Table
|
|
if (passno == 2) {
|
|
if (lstadr < 0) hexadr[0] = 0; else sprintf(hexadr, "%04X", lstadr);
|
|
fprintf(lstfil, "%05d %-4s %-9s%-7s %-5s %-16s %s\n", lineno, hexadr, mcode, label, mnmnc, oprnd, cmmnt );
|
|
fflush(lstfil); //Flush Output Buffer in case of Segmentation Fault
|
|
}
|
|
lineno++;
|
|
if (incnam[0] && incfil == NULL) opninc(); //Open Include File
|
|
}
|
|
endadr = curadr; //Set End Address for Second Pass
|
|
if (debug) printf("End address set to %04x\n", endadr);
|
|
}
|
|
|
|
/* Parse Command Line Option */
|
|
int pcoptn(char *argval) {
|
|
if (argval[0] != '-') return FALSE;
|
|
char option = toupper(argval[1]);
|
|
if (debug) printf(" Option '%c'\n", option);
|
|
switch(option) {
|
|
case 'D': debug = TRUE; break; //Enable debug Output
|
|
case 'A': objtyp = APLFIL; break; //Apple-1 Monitor Entry File
|
|
case 'E': objtyp = ATRFIL; break; //Atari 8-Bit EXE File
|
|
case 'P': objtyp = PRGFIL; break; //Commodore PRG File
|
|
case 'S': objtyp = MSRFIL; break; //Motorola SREC File
|
|
case 'X': objtyp = INTFIL; break; //Intel HEX File
|
|
default: xerror("Illegal Command Line Option %s\n", argval);
|
|
}
|
|
if (debug && objtyp) printf("Object type set to %d\n", objtyp);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Parse Command Line Arguments */
|
|
void pcargs(int argc, char *argv[]) {
|
|
int argnum = 0;
|
|
for (int arg = 0; arg<argc; arg++) {
|
|
if (debug) printf("Arg %d='%s'\n", arg, argv[arg]);
|
|
if (arg == 0) {strcpy(prgnam, argv[arg]); continue;}
|
|
if (strcmp(argv[arg], "-i") ==0) {
|
|
strcpy(incpath, argv[++arg]);
|
|
if (debug) printf("Include Path set to \"%s\"\n", incpath);
|
|
continue;
|
|
}
|
|
if (pcoptn(argv[arg])) continue;
|
|
switch (argnum++) {
|
|
case 0: strcpy(inpnam, argv[arg]); break;
|
|
case 1: strcpy(outnam, argv[arg]); break;
|
|
case 2: strcpy(lstnam, argv[arg]); break;
|
|
default: xerror("Too Many Arguments\n", "");
|
|
}
|
|
}
|
|
if (argnum<2) usage(argv[0]);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
debug = DEBUG; //Initialize Debug Flag
|
|
incpath[0] = 0; //Initialize Include File Path
|
|
lstnam[0] = 0; lstfil = NULL; //Default to No List File
|
|
incnam[0] = 0; incfil = NULL; //Include File Not Opened
|
|
lineno = 0; //No Line Number (yet)
|
|
objtyp = BINFIL; //Default Object File Type to Binary
|
|
pcargs(argc, argv); //Parse Command Line Arguments
|
|
inpfil = opnfil(inpnam, "r", ""); //Open Input File
|
|
outfil = opnobj(outnam, ""); //Open Output File
|
|
if (lstnam[0]) //If List File Name Specified
|
|
lstfil = opnfil(lstnam, "w", ""); // Open List File
|
|
asmfil(1); //Assemble Input File (First Pass)
|
|
asmfil(2); //Assemble Input File (First Pass)
|
|
if (lstfil && symcnt) prtsym(); //Print Symbol Table
|
|
clsobj(); //Close Object File
|
|
exit(0); //Exit with No Errors
|
|
}
|
|
|