Source code for a macro assembler
This appendix gives the complete source code for the macro assembler for the single-accumulator machine discussed in Chapter 7.
----- assemble.cpp -------------------------------------------------------------
// Macro assembler/interpreter for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "mc.h"
#include "as.h"
#define version "Macro Assembler 1.0"
#define usage "Usage: ASSEMBLE source [listing]\n"
void main(int argc, char *argv[])
{ bool errors;
char reply;
char sourcename[256], listname[256];
// check on correct parameter usage
if (argc == 1) { printf(usage); exit(1); }
strcpy(sourcename, argv[1]);
if (argc > 2) strcpy(listname, argv[2]);
else appendextension(sourcename, ".lst", listname);
MC *Machine = new(MC);
AS *Assembler = new AS(sourcename, listname, version, Machine);
Assembler->assemble(errors);
if (errors)
{ printf("\nAssembly failed\n"); }
else
{ printf("\nAssembly successful\n");
while (true)
{ printf("\nInterpret? (y/n) ");
do
{ scanf("%c", &reply);
} while (toupper(reply) != 'N' && toupper(reply) != 'Y');
if (toupper(reply) == 'N') break;
scanf("%*[^\n]"); getchar();
Machine->interpret();
}
}
delete Machine;
delete Assembler;
}
----- misc.h ------------------------------------------------------------------
// Various common items for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef MISC_H
#define MISC_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#define boolean int
#define bool int
#define true 1
#define false 0
#define TRUE 1
#define FALSE 0
#define maxint INT_MAX
#if __MSDOS__ || MSDOS || WIN32 || __WIN32__
# define pathsep '\\'
#else
# define pathsep '/'
#endif
static void appendextension (char *oldstr, char *ext, char *newstr)
// Changes filename in oldstr from PRIMARY.xxx to PRIMARY.ext in newstr
{ int i;
char old[256];
strcpy(old, oldstr);
i = strlen(old);
while ((i > 0) && (old[i-1] != '.') && (old[i-1] != pathsep)) i--;
if ((i > 0) && (old[i-1] == '.')) old[i-1] = 0;
if (ext[0] == '.') sprintf(newstr,"%s%s", old, ext);
else sprintf(newstr, "%s.%s", old, ext);
}
#define ASM_alength 8 // maximum length of mnemonics, labels
#define ASM_slength 35 // maximum length of comment and other strings
typedef char ASM_alfa[ASM_alength + 1];
typedef char ASM_strings[ASM_slength + 1];
#include "set.h"
enum ASM_errors {
ASM_invalidcode, ASM_undefinedlabel, ASM_invalidaddress,
ASM_unlabelled, ASM_hasaddress, ASM_noaddress,
ASM_excessfields, ASM_mismatched, ASM_nonalpha,
ASM_badlabel, ASM_invalidchar, ASM_invalidquote,
ASM_overflow
};
typedef Set<ASM_overflow> ASM_errorset;
#endif /* MISC_H */
----- set.h -------------------------------------------------------------------
// Simple set operations
#ifndef SET_H
#define SET_H
template <int maxElem>
class Set { // { 0 .. maxElem }
public:
Set() // Construct { }
{ clear(); }
Set(int e1) // Construct { e1 }
{ clear(); incl(e1); }
Set(int e1, int e2) // Construct { e1, e2 }
{ clear(); incl(e1); incl(e2); }
Set(int e1, int e2, int e3) // Construct { e1, e2, e3 }
{ clear(); incl(e1); incl(e2); incl(e3); }
Set(int n, int e1[]) // Construct { e[0] .. e[n-1] }
{ clear(); for (int i = 0; i < n; i++) incl(e1[i]); }
void incl(int e) // Include e
{ if (e >= 0 && e <= maxElem) bits[wrd(e)] |= bitmask(e); }
void excl(int e) // Exclude e
{ if (e >= 0 && e <= maxElem) bits[wrd(e)] &= ~bitmask(e); }
int memb(int e) // Test membership for e
{ if (e >= 0 && e <= maxElem) return((bits[wrd(e)] & bitmask(e)) != 0);
else return 0;
}
int isempty(void) // Test for empty set
{ for (int i = 0; i < length; i++) if (bits[i]) return 0;
return 1;
}
Set operator + (const Set &s) // Union with s
{ Set<maxElem> r;
for (int i = 0; i < length; i++) r.bits[i] = bits[i] | s.bits[i];
return r;
}
Set operator * (const Set &s) // Intersection with s
{ Set<maxElem> r;
for (int i = 0; i < length; i++) r.bits[i] = bits[i] & s.bits[i];
return r;
}
Set operator - (const Set &s) // Difference with s
{ Set<maxElem> r;
for (int i = 0; i < length; i++) r.bits[i] = bits[i] & ~s.bits[i];
return r;
}
Set operator / (const Set &s) // Symmetric difference with s
{ Set<maxElem> r;
for (int i = 0; i < length; i++) r.bits[i] = bits[i] ^ s.bits[i];
return r;
}
private:
unsigned char bits[(maxElem + 8) / 8];
int length;
int wrd(int i) { return(i / 8); }
int bitmask(int i) { return(1 << (i % 8)); }
void clear() { length = (maxElem + 8) / 8;
for (int i = 0; i < length; i++) bits[i] = 0;
}
};
#endif /* SET_H */
----- sh.s --------------------------------------------------------------------
// Source handler for assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef SH_H
#define SH_H
#include "misc.h"
const int linemax = 129; // limit on source line length
class SH {
public:
FILE *lst; // listing file
char ch; // latest character read
void nextch(void);
// Returns ch as the next character on current source line, reading a new
// line where necessary. ch is returned as NUL if src is exhausted
bool endline(void) { return (charpos == linelength); }
// Returns true when end of current line has been reached
bool startline(void) { return (charpos == 1); }
// Returns true if current ch is the first on a line
void writehex(int i, int n) { fprintf(lst, "%02X%*c", i, n-2, ' '); }
// Writes (byte valued) i to lst file as hex pair, left-justified in n spaces
void writetext(char *s, int n) { fprintf(lst, "%-*s", n, s); }
// Writes s to lst file left-justified in n spaces
SH();
// Default constructor
SH(char *sourcename, char *listname, char *version);
// Initializes source handler, and displays version information on lst file.
// Opens src and lst files using given names
~SH();
// Closes src and lst files
private:
FILE *src; // source file
int charpos; // character pointer
int linelength; // line length
char line[linemax + 1]; // last line read
};
#endif /*SH_H*/
----- sh.cpp ------------------------------------------------------------------
// Source handler for assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "sh.h"
void SH::nextch(void)
{ if (ch == '\0') return; // input exhausted
if (charpos == linelength) // new line needed
{ linelength = 0; charpos = 0; ch = getc(src);
while (ch != '\n' && !feof(src))
{ if (linelength < linemax) { line[linelength] = ch; linelength++; }
ch = getc(src);
}
if (feof(src))
line[linelength] = '\0'; // mark end with an explicit nul
else
line[linelength] = ' '; // mark end with an explicit space
linelength++;
}
ch = line[charpos]; charpos++; // pass back unique character
}
SH::SH(char *sourcename, char *listname, char *version)
{ src = fopen(sourcename, "r");
if (src == NULL)
{ printf("Could not open input file\n"); exit(1); }
lst = fopen(listname, "w");
if (lst == NULL)
{ printf("Could not open listing file\n"); lst = stdout; }
fprintf(lst, "%s\n\n", version);
ch = ' '; charpos = 0; linelength = 0;
}
SH::SH()
{ src = NULL; lst = NULL; ch = ' '; charpos = 0; linelength = 0; }
SH::~SH()
{ if (src) fclose(src); src = NULL;
if (lst) fclose(lst); lst = NULL;
}
----- la.h --------------------------------------------------------------------
// Lexical analyzer for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef LA_H
#define LA_H
#include "misc.h"
#include "sh.h"
enum LA_symtypes {
LA_unknown, LA_eofsym, LA_eolsym, LA_idsym, LA_numsym, LA_comsym,
LA_commasym, LA_plussym, LA_minussym, LA_starsym
};
struct LA_symbols {
bool islabel; // if in first column
LA_symtypes sym; // class
ASM_strings str; // lexeme
int num; // value if numeric
};
class LA {
public:
void getsym(LA_symbols &SYM, ASM_errorset &errors);
// Returns the next symbol on current source line.
// Adds to set of errors if necessary and returns SYM.sym = unknown
// if no valid symbol can be recognized
LA(SH *S);
// Associates scanner with source handler S and initializes scanning
private:
SH *Srce;
void getword(LA_symbols &SYM);
void getnumber(LA_symbols &SYM, ASM_errorset &errors);
void getcomment(LA_symbols &SYM);
void getquotedchar(LA_symbols &SYM, char quote, ASM_errorset &errors);
};
#endif /*LA_H*/
----- la.cpp ------------------------------------------------------------------
// Lexical analyzer for assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "la.h"
void LA::getword(LA_symbols &SYM)
// Assemble identifier or opcode, in UPPERCASE for consistency
{ int length = 0;
while (isalnum(Srce->ch))
{ if (length < ASM_slength)
{ SYM.str[length] = toupper(Srce->ch); length++; }
Srce->nextch();
}
SYM.str[length] = '\0';
}
void LA::getnumber(LA_symbols &SYM, ASM_errorset &errors)
// Assemble number and store its identifier in UPPERCASE for consistency
{ int length = 0;
while (isdigit(Srce->ch))
{ SYM.num = SYM.num * 10 + Srce->ch - '0';
if (SYM.num > 255) errors.incl(ASM_overflow);
SYM.num %= 256;
if (length < ASM_slength) { SYM.str[length] = toupper(Srce->ch); length++; }
Srce->nextch();
}
SYM.str[length] = '\0';
}
void LA::getcomment(LA_symbols &SYM)
// Assemble comment
{ int length = 0;
while (!Srce->endline())
{ if (length < ASM_slength) { SYM.str[length] = Srce->ch; length++; }
Srce->nextch();
}
SYM.str[length] = '\0';
}
void LA::getquotedchar(LA_symbols &SYM, char quote, ASM_errorset &errors)
// Assemble single character address token
{ SYM.str[0] = quote;
Srce->nextch(); SYM.num = Srce->ch; SYM.str[1] = Srce->ch;
if (!Srce->endline()) Srce->nextch();
SYM.str[2] = Srce->ch; SYM.str[3] = '\0';
if (Srce->ch != quote) errors.incl(ASM_invalidquote);
if (!Srce->endline()) Srce->nextch();
}
void LA::getsym(LA_symbols &SYM, ASM_errorset &errors)
{ SYM.num = 0; SYM.str[0] = '\0'; // empty string
while (Srce->ch == ' ' && !Srce->endline()) Srce->nextch();
SYM.islabel = (Srce->startline() && Srce->ch != ' '
&& Srce->ch != ';' && Srce->ch != '\0');
if (SYM.islabel && !isalpha(Srce->ch)) errors.incl(ASM_badlabel);
if (Srce->ch == '\0') { SYM.sym = LA_eofsym; return; }
if (Srce->endline()) { SYM.sym = LA_eolsym; Srce->nextch(); return; }
if (isalpha(Srce->ch))
{ SYM.sym = LA_idsym; getword(SYM); }
else if (isdigit(Srce->ch))
{ SYM.sym = LA_numsym; getnumber(SYM, errors); }
else switch (Srce->ch)
{ case ';':
SYM.sym = LA_comsym; getcomment(SYM); break;
case ',':
SYM.sym = LA_commasym; strcpy(SYM.str, ","); Srce->nextch(); break;
case '+':
SYM.sym = LA_plussym; strcpy(SYM.str, "+"); Srce->nextch(); break;
case '-':
SYM.sym = LA_minussym; strcpy(SYM.str, "-"); Srce->nextch(); break;
case '*':
SYM.sym = LA_starsym; strcpy(SYM.str, "*"); Srce->nextch(); break;
case '\'':
case '"':
SYM.sym = LA_numsym; getquotedchar(SYM, Srce->ch, errors); break;
default:
SYM.sym = LA_unknown; getcomment(SYM); errors.incl(ASM_invalidchar);
break;
}
}
LA::LA(SH* S)
{ Srce = S; Srce->nextch(); }
----- sa.h --------------------------------------------------------------------
// Syntax analyzer for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef SA_H
#define SA_H
#include "misc.h"
#include "la.h"
const int SA_maxterms = 16;
enum SA_termkinds {
SA_absent, SA_numeric, SA_alphameric, SA_comma, SA_plus, SA_minus, SA_star
};
struct SA_terms {
SA_termkinds kind;
int number; // value if known
ASM_alfa name; // character representation
};
struct SA_addresses {
char length; // number of fields
SA_terms term[SA_maxterms - 1];
};
struct SA_unpackedlines {
// source text, unpacked into fields
bool labelled;
ASM_alfa labfield, mnemonic;
SA_addresses address;
ASM_strings comment;
ASM_errorset errors;
};
class SA {
public:
void parse(SA_unpackedlines &srcline);
// Analyzes the next source line into constituent fields
SA(LA *L);
// Associates syntax analyzer with its lexical analyzer L
private:
LA *Lex;
LA_symbols SYM;
void GetSym(ASM_errorset &errors);
void getaddress(SA_unpackedlines &srcline);
};
#endif /*SA_H*/
----- sa.cpp ------------------------------------------------------------------
// Syntax analyzer for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "sa.h"
#include "set.h"
typedef Set<LA_starsym> symset;
void SA::GetSym(ASM_errorset &errors)
{ Lex->getsym(SYM, errors); }
void SA::getaddress(SA_unpackedlines &srcline)
// Unpack the addressfield of line into srcline
{ symset allowed(LA_idsym, LA_numsym, LA_starsym);
symset possible = allowed + symset(LA_commasym, LA_plussym, LA_minussym);
srcline.address.length = 0;
while (possible.memb(SYM.sym))
{ if (!allowed.memb(SYM.sym))
srcline.errors.incl(ASM_invalidaddress);
if (srcline.address.length < SA_maxterms - 1)
srcline.address.length++;
else
srcline.errors.incl(ASM_excessfields);
sprintf(srcline.address.term[srcline.address.length - 1].name, "%.*s",
ASM_alength, SYM.str);
srcline.address.term[srcline.address.length - 1].number = SYM.num;
switch (SYM.sym)
{ case LA_numsym:
srcline.address.term[srcline.address.length - 1].kind = SA_numeric;
break;
case LA_idsym:
srcline.address.term[srcline.address.length - 1].kind = SA_alphameric;
break;
case LA_plussym:
srcline.address.term[srcline.address.length - 1].kind = SA_plus;
break;
case LA_minussym:
srcline.address.term[srcline.address.length - 1].kind = SA_minus;
break;
case LA_starsym:
srcline.address.term[srcline.address.length - 1].kind = SA_star;
break;
case LA_commasym:
srcline.address.term[srcline.address.length - 1].kind = SA_comma;
break;
}
allowed = possible - allowed;
GetSym(srcline.errors); // check trailing comment, parameters
}
if (!(srcline.address.length & 1)) srcline.errors.incl(ASM_invalidaddress);
}
void SA::parse(SA_unpackedlines &srcline)
{ symset startaddress(LA_idsym, LA_numsym, LA_starsym);
srcline.labfield[0] = '\0';
strcpy(srcline.mnemonic, " ");
srcline.comment[0] = '\0';
srcline.errors = ASM_errorset();
srcline.address.term[0].kind = SA_absent;
srcline.address.term[0].number = 0;
srcline.address.term[0].name[0] = '\0';
srcline.address.length = 0;
GetSym(srcline.errors); // first on line - opcode or label ?
if (SYM.sym == LA_eofsym) { strcpy(srcline.mnemonic, "END"); return; }
srcline.labelled = SYM.islabel;
if (srcline.labelled) // must look for the opcode
{ srcline.labelled = srcline.errors.isempty();
sprintf(srcline.labfield, "%.*s", ASM_alength, SYM.str);
GetSym(srcline.errors); // probably an opcode
}
if (SYM.sym == LA_idsym) // has a mnemonic
{ sprintf(srcline.mnemonic, "%.*s", ASM_alength, SYM.str);
GetSym(srcline.errors); // possibly an address
if (startaddress.memb(SYM.sym)) getaddress(srcline);
}
if (SYM.sym == LA_comsym || SYM.sym == LA_unknown)
{ strcpy(srcline.comment, SYM.str); GetSym(srcline.errors); }
if (SYM.sym != LA_eolsym) // spurious symbol
{ strcpy(srcline.comment, SYM.str); srcline.errors.incl(ASM_excessfields); }
while (SYM.sym != LA_eolsym && SYM.sym != LA_eofsym)
GetSym(srcline.errors); // consume garbage
}
SA::SA(LA * L)
{ Lex = L; }
----- st.h --------------------------------------------------------------------
// Table handler for one-pass macro assembler for single-accumulator machine
// Version using simple linked list
// P.D. Terry, Rhodes University, 1996
#ifndef ST_H
#define ST_H
#include "misc.h"
#include "mc.h"
#include "sh.h"
enum ST_actions { ST_add, ST_subtract };
typedef void (*ST_patch)(MC_bytes mem[], MC_bytes b, MC_bytes v, ST_actions a);
struct ST_forwardrefs { // forward references for undefined labels
MC_bytes byte; // to be patched
ST_actions action; // taken when patching
ST_forwardrefs *nlink; // to next reference
};
struct ST_entries {
ASM_alfa name; // name
MC_bytes value; // value once defined
bool defined; // true after defining occurrence encountered
ST_entries *slink; // to next entry
ST_forwardrefs *flink; // to forward references
};
class ST {
public:
void printsymboltable(bool &errors);
// Summarizes symbol table at end of assembly, and alters errors to true if
// any symbols have remained undefined
void enter(char *name, MC_bytes value);
// Adds name to table with known value
void valueofsymbol(char *name, MC_bytes location, MC_bytes &value,
ST_actions action, bool &undefined);
// Returns value of required name, and sets undefined if not found.
// Records action to be applied later in fixing up forward references.
// location is the current value of the instruction location counter
void outstandingreferences(MC_bytes *mem, ST_patch fix);
// Walks symbol table, applying fix to outstanding references in mem
ST(SH *S);
// Associates table handler with source handler S (for listings)
private:
SH *Srce;
ST_entries *lastsym;
void findentry(ST_entries *&symentry, char *name, bool &found);
};
#endif /*ST_H*/
----- st.cpp ------------------------------------------------------------------
// Table handler for one-pass macro assembler for single-accumulator machine
// Version using simply linked list
// P.D. Terry, Rhodes University, 1996
#include "st.h"
void ST::printsymboltable(bool &errors)
{ fprintf(Srce->lst, "\nSymbol Table\n");
fprintf(Srce->lst, "------------\n");
ST_entries *symentry = lastsym;
while (symentry)
{ Srce->writetext(symentry->name, 10);
if (!symentry->defined)
{ fprintf(Srce->lst, " --- undefined"); errors = true; }
else
{ Srce->writehex(symentry->value, 3);
fprintf(Srce->lst, "%5d", symentry->value);
}
putc('\n', Srce->lst);
symentry = symentry->slink;
}
putc('\n', Srce->lst);
}
void ST::findentry(ST_entries *&symentry, char *name, bool &found)
{ symentry = lastsym;
found = false;
while (!found && symentry)
{ if (!strcmp(name, symentry->name))
found = true;
else
symentry = symentry->slink;
}
if (found) return;
symentry = new ST_entries; // make new forward reference entry
sprintf(symentry->name, "%.*s", ASM_alength, name);
symentry->value = 0;
symentry->defined = false;
symentry->flink = NULL;
symentry->slink = lastsym;
lastsym = symentry;
}
void ST::enter(char *name, MC_bytes value)
{ ST_entries *symentry;
bool found;
findentry(symentry, name, found);
symentry->value = value;
symentry->defined = true;
}
void ST::valueofsymbol(char *name, MC_bytes location, MC_bytes &value,
ST_actions action, bool &undefined)
{ ST_entries *symentry;
ST_forwardrefs *forwardentry;
bool found;
findentry(symentry, name, found);
value = symentry->value;
undefined = !symentry->defined;
if (!undefined) return;
forwardentry = new ST_forwardrefs; // new node in reference chain
forwardentry->byte = location; forwardentry->action = action;
if (found) // it was already in the table
forwardentry->nlink = symentry->flink;
else // new entry in the table
forwardentry->nlink = NULL;
symentry->flink = forwardentry;
}
void ST::outstandingreferences(MC_bytes mem[], ST_patch fix)
{ ST_forwardrefs *link;
ST_entries *symentry = lastsym;
while (symentry)
{ link = symentry->flink;
while (link)
{ fix(mem, link->byte, symentry->value, link->action);
link = link->nlink;
}
symentry = symentry->slink;
}
}
ST::ST(SH *S)
{ Srce = S; lastsym = NULL; }
----- st.h --------------------------------------------------------------------
// Table handler for one-pass macro assembler for single-accumulator machine
// Version using hashing technique with collision stepping
// P.D. Terry, Rhodes University, 1996
#ifndef ST_H
#define ST_H
#include "misc.h"
#include "mc.h"
#include "sh.h"
const int tablemax = 239; // symbol table size
const int tablestep = 7; // a prime number
enum ST_actions { ST_add, ST_subtract };
typedef void (*ST_patch)(MC_bytes mem[], MC_bytes b, MC_bytes v, ST_actions a);
typedef short tableindex;
struct ST_forwardrefs { // forward references for undefined labels
MC_bytes byte; // to be patched
ST_actions action; // taken when patching
ST_forwardrefs *nlink; // to next reference
};
struct ST_entries {
ASM_alfa name; // name
MC_bytes value; // value once defined
bool used; // true when in use already
bool defined; // true after defining occurrence encountered
ST_forwardrefs *flink; // to forward references
};
class ST {
public:
void printsymboltable(bool &errors);
// Summarizes symbol table at end of assembly, and alters errors
// to true if any symbols have remained undefined
void enter(char *name, MC_bytes value);
// Adds name to table with known value
void valueofsymbol(char *name, MC_bytes location, MC_bytes &value,
ST_actions action, bool &undefined);
// Returns value of required name, and sets undefined if not found.
// Records action to be applied later in fixing up forward references.
// location is the current value of the instruction location counter
void outstandingreferences(MC_bytes mem[], ST_patch fix);
// Walks symbol table, applying fix to outstanding references in mem
ST(SH *S);
// Associates table handler with source handler S (for listings)
private:
SH *Srce;
ST_entries hashtable[tablemax + 1];
void findentry(tableindex &symentry, char *name, bool &found);
};
#endif /*ST_H*/
----- st.cpp ------------------------------------------------------------------
// Table handler for one-pass macro assembler for single-accumulator machine
// Version using hashing technique with collision stepping
// P.D. Terry, Rhodes University, 1996
#include "st.h"
void ST::printsymboltable(bool &errors)
{ fprintf(Srce->lst, "\nSymbol Table\n");
fprintf(Srce->lst, "------------\n");
for (tableindex i = 0; i < tablemax; i++)
{ if (hashtable[i].used)
{ Srce->writetext(hashtable[i].name, 10);
if (!hashtable[i].defined)
{ fprintf(Srce->lst, " --- undefined"); errors = true; }
else
{ Srce->writehex(hashtable[i].value, 3);
fprintf(Srce->lst, "%5d", hashtable[i].value);
}
putc('\n', Srce->lst);
}
}
putc('\n', Srce->lst);
}
tableindex hashkey(char *ident)
{ const int large = (maxint - 256); // large number in hashing function
int sum = 0, l = strlen(ident);
for (int i = 0; i < l; i++) sum = (sum + ident[i]) % large;
return (sum % tablemax);
}
void ST::findentry(tableindex &symentry, char *name, bool &found)
{ enum { looking, entered, caninsert, overflow } state;
symentry = hashkey(name);
state = looking;
tableindex start = symentry;
while (state == looking)
{ if (!hashtable[symentry].used)
{ state = caninsert; break; }
if (!strcmp(name, hashtable[symentry].name))
{ state = entered; break; }
symentry = (symentry + tablestep) % tablemax;
if (symentry == start) state = overflow;
}
switch (state)
{ case caninsert:
sprintf(hashtable[symentry].name, "%.*s", ASM_alength, name);
hashtable[symentry].value = 0;
hashtable[symentry].used = true;
hashtable[symentry].flink = NULL;
hashtable[symentry].defined = false;
break;
case overflow:
printf("Symbol table overflow\n");
exit(1);
break;
case entered: // no further action
break;
}
found = (state == entered);
}
void ST::enter(char *name, MC_bytes value)
{ tableindex symentry;
bool found;
findentry(symentry, name, found);
hashtable[symentry].value = value;
hashtable[symentry].defined = true;
}
void ST::valueofsymbol(char *name, MC_bytes location, MC_bytes &value,
ST_actions action, bool &undefined)
{ tableindex symentry;
ST_forwardrefs *forwardentry;
bool found;
findentry(symentry, name, found);
value = hashtable[symentry].value;
undefined = !hashtable[symentry].defined;
if (!undefined) return;
forwardentry = new ST_forwardrefs; // new node in reference chain
forwardentry->byte = location;
forwardentry->action = action;
if (found) // it was already in the table
forwardentry->nlink = hashtable[symentry].flink;
else // new entry in the table
forwardentry->nlink = NULL;
hashtable[symentry].flink = forwardentry;
}
void ST::outstandingreferences(MC_bytes mem[], ST_patch fix)
{ ST_forwardrefs *link;
for (tableindex i = 0; i < tablemax; i++)
{ if (hashtable[i].used)
{ link = hashtable[i].flink;
while (link)
{ fix(mem, link->byte, hashtable[i].value, link->action);
link = link->nlink;
}
}
}
}
ST::ST(SH *S)
{ Srce = S;
for (tableindex i = 0; i < tablemax; i++) hashtable[i].used = false;
}
----- mh.h --------------------------------------------------------------------
// Macro analyzer for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef MH_H
#define MH_H
#include "asmbase.h"
typedef struct MH_macentries *MH_macro;
class MH {
public:
void newmacro(MH_macro &m, SA_unpackedlines header);
// Creates m as a new macro, with given header line that includes the
// formal parameters
void storeline(MH_macro m, SA_unpackedlines line);
// Adds line to the definition of macro m
void checkmacro(char *name, MH_macro &m, bool &ismacro, int ¶ms);
// Checks to see whether name is that of a predefined macro. Returns
// ismacro as the result of the search. If successful, returns m as
// the macro, and params as the number of formal parameters
void expand(MH_macro m, SA_addresses actualparams,
ASMBASE *assembler, bool &errors);
// Expands macro m by invoking assembler for each line of the macro
// definition, and using the actualparams supplied in place of the
// formal parameters appearing in the macro header.
// errors is altered to true if the assembly fails for any reason
MH();
// Initializes macro handler
private:
MH_macro lastmac;
int position(MH_macro m, char *str);
void substituteactualparameters(MH_macro m,
SA_addresses actualparams,
SA_unpackedlines &nextline);
};
#endif /*MH_H*/
----- mh.cpp ------------------------------------------------------------------
// Macro analyzer for macro assemblers for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "misc.h"
#include "mh.h"
struct MH_lines {
SA_unpackedlines text; // a single line of macro text
MH_lines *link; // link to the next line in the macro
};
struct MH_macentries {
SA_unpackedlines definition; // header line
MH_macro mlink; // link to next macro in list
MH_lines *firstline, *lastline; // links to the text of this macro
};
void MH::newmacro(MH_macro &m, SA_unpackedlines header)
{ m = new MH_macentries;
m->definition = header; // store formal parameters
m->firstline = NULL; // no text yet
m->mlink = lastmac; // link to rest of macro definitions
lastmac = m; // and this becomes the last macro added
}
void MH::storeline(MH_macro m, SA_unpackedlines line)
{ MH_lines *newline = new MH_lines;
newline->text = line; // store source line
newline->link = NULL; // at the end of the queue
if (m->firstline == NULL) // first line of macro?
m->firstline = newline; // form head of new queue
else
m->lastline->link = newline; // add to tail of existing queue
m->lastline = newline;
}
void MH::checkmacro(char *name, MH_macro &m, bool &ismacro, int ¶ms)
{ m = lastmac; ismacro = false; params = 0;
while (m && !ismacro)
{ if (!strcmp(name, m->definition.labfield))
{ ismacro = true; params = m->definition.address.length; }
else
m = m->mlink;
}
}
int MH::position(MH_macro m, char *str)
// Search formals for match to str; returns 0 if no match
{ bool found = false;
int i = m->definition.address.length - 1;
while (i >= 0 && !found)
{ if (!strcmp(str, m->definition.address.term[i].name))
found = true;
else
i--;
}
return i;
}
void MH::substituteactualparameters(MH_macro m,
SA_addresses actualparams, SA_unpackedlines &nextline)
// Substitute label, mnemonic or address components into
// nextline where necessary
{ int j = 0, i = position(m, nextline.labfield); // check label
if (i >= 0) strcpy(nextline.labfield, actualparams.term[i].name);
i = position(m, nextline.mnemonic); // check mnemonic
if (i >= 0) strcpy(nextline.mnemonic, actualparams.term[i].name);
j = 0; // check address fields
while (j < nextline.address.length)
{ i = position(m, nextline.address.term[j].name);
if (i >= 0) nextline.address.term[j] = actualparams.term[i];
j += 2; // bypass commas
}
}
void MH::expand(MH_macro m, SA_addresses actualparams,
ASMBASE *assembler, bool &errors)
{ SA_unpackedlines nextline;
if (!m) return; // nothing to do
MH_lines *current = m->firstline;
while (current)
{ nextline = current->text; // retrieve line of macro text
substituteactualparameters(m, actualparams, nextline);
assembler->assembleline(nextline, errors); // and asssemble it
current = current->link;
}
}
MH::MH()
{ lastmac = NULL; }
----- asmbase.h ---------------------------------------------------------------
// Base assembler class for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef ASMBASE_H
#define ASMBASE_H
#include "misc.h"
#include "sa.h"
class ASMBASE {
public:
virtual void assembleline(SA_unpackedlines &srcline, bool &failure) = 0;
// Assemble srcline, reporting failure if it occurs
};
#endif /*A_H*/
----- as.h --------------------------------------------------------------------
// One-pass macro assembler for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#ifndef AS_H
#define AS_H
#include "asmbase.h"
#include "mc.h"
#include "st.h"
#include "sh.h"
#include "mh.h"
class AS : ASMBASE {
public:
void assemble(bool &errors);
// Assembles and lists program.
// Assembled code is dumped to file for later interpretation, and left
// in pseudo-machine memory for immediate interpretation if desired.
// Returns errors = true if assembly fails
virtual void assembleline(SA_unpackedlines &srcline, bool &failure);
// Assemble srcline, reporting failure if it occurs
AS(char *sourcename, char *listname, char *version, MC *M);
// Instantiates version of the assembler to process sourcename, creating
// listings in listname, and generating code for associated machine M
private:
SH *Srce;
LA *Lex;
SA *Parser;
ST *Table;
MC *Machine;
MH *Macro;
struct { ASM_alfa spelling; MC_bytes byte; } optable[256];
int opcodes; // number of opcodes actually defined
struct objlines { MC_bytes location, opcode, address; };
objlines objline; // current line as assembled
MC_bytes location; // location counter
bool assembling; // monitor progress of assembly
bool include; // handle conditional assembly
MC_bytes bytevalue(char *mnemonic);
void enter(char *mnemonic, MC_bytes thiscode);
void termvalue(SA_terms term, MC_bytes &value, ST_actions action,
bool &undefined, bool &badaddress);
void evaluate(SA_addresses address, MC_bytes &value,
bool &undefined, bool &malformed);
void listerrors(ASM_errorset allerrors, bool &failure);
void listcode(void);
void listsourceline(SA_unpackedlines &srcline, bool coderequired,
bool &failure);
void definemacro(SA_unpackedlines &srcline, bool &failure);
void firstpass(bool &errors);
};
#endif /*AS_H*/
----- as.cpp ------------------------------------------------------------------
// One-pass macro assembler for the single-accumulator machine
// P.D. Terry, Rhodes University, 1996
#include "as.h"
const bool nocodelisted = false;
const bool codelisted = true;
enum directives {
AS_err = 61, // erroneous opcode
AS_nul = 62, // blank opcode
AS_beg = 63, // introduce program
AS_end = 64, // end of source
AS_mac = 65, // introduce macro
AS_ds = 66, // define storage
AS_equ = 67, // equate
AS_org = 68, // set location counter
AS_if = 69, // conditional
AS_dc = 70 // define constant byte
};
MC_bytes AS::bytevalue(char *mnemonic)
{ int look, l = 1, r = opcodes;
do // binary search
{ look = (l + r) / 2;
if (strcmp(mnemonic, optable[look].spelling) <= 0) r = look - 1;
if (strcmp(mnemonic, optable[look].spelling) >= 0) l = look + 1;
} while (l <= r);
if (l > r + 1)
return (optable[look].byte); // found it
else
return (optable[0].byte); // err entry
}
void AS::enter(char *mnemonic, MC_bytes thiscode)
// Add (mnemonic, thiscode) to optable for future look up
{ strcpy(optable[opcodes].spelling, mnemonic);
optable[opcodes].byte = thiscode;
opcodes++;
}
void backpatch(MC_bytes mem[], MC_bytes location, MC_bytes value, ST_actions how)
{ switch (how)
{ case ST_add:
mem[location] = (mem[location] + value) % 256; break;
case ST_subtract:
mem[location] = (mem[location] - value + 256) % 256; break;
}
}
void AS::termvalue(SA_terms term, MC_bytes &value, ST_actions action,
bool &undefined, bool &badaddress)
// Determine value of a single term, recording outstanding action
// if undefined so far, and recording badaddress if malformed
{ undefined = false;
switch (term.kind)
{ case SA_absent:
case SA_numeric:
value = term.number % 256; break;
case SA_star:
value = location; break;
case SA_alphameric:
Table->valueofsymbol(term.name, location, value, action, undefined); break;
default:
badaddress = true; value = 0; break;
}
}
void AS::evaluate(SA_addresses address, MC_bytes &value, bool &undefined,
bool &malformed)
// Determine value of address, recording whether undefined or malformed
{ ST_actions nextaction;
MC_bytes nextvalue;
bool unknown;
malformed = false;
termvalue(address.term[0], value, ST_add, undefined, malformed);
int i = 1;
while (i < address.length)
{ switch (address.term[i].kind)
{ case SA_plus: nextaction = ST_add; break;
case SA_minus: nextaction = ST_subtract; break;
default: nextaction = ST_add; malformed = true; break;
}
i++;
termvalue(address.term[i], nextvalue, nextaction, unknown, malformed);
switch (nextaction)
{ case ST_add: value = (value + nextvalue) % 256; break;
case ST_subtract: value = (value - nextvalue + 256) % 256; break;
}
undefined = (undefined || unknown);
i++;
}
}
static char *ErrorMsg[] = {
" - unknown opcode",
" - address field not resolved",
" - invalid address field",
" - label missing",
" - spurious address field",
" - address field missing",
" - address field too long",
" - wrong number of parameters",
" - invalid formal parameters",
" - invalid label",
" - unknown character",
" - mismatched quotes",
" - number too large",
};
void AS::listerrors(ASM_errorset allerrors, bool &failure)
{ if (allerrors.isempty()) return;
failure = true;
fprintf(Srce->lst, "Next line has errors");
for (int error = ASM_invalidcode; error <= ASM_overflow; error++)
if (allerrors.memb(error)) fprintf(Srce->lst, "%s\n", ErrorMsg[error]);
}
void AS::listcode(void)
// List generated code bytes on source listing
{ Srce->writehex(objline.location, 4);
if (objline.opcode >= AS_err && objline.opcode <= AS_if)
fprintf(Srce->lst, " ");
else if (objline.opcode <= MC_hlt) // OneByteOps
Srce->writehex(objline.opcode, 7);
else if (objline.opcode == AS_dc) // DC special case
Srce->writehex(objline.address, 7);
else // TwoByteOps
{ Srce->writehex(objline.opcode, 3);
Srce->writehex(objline.address, 4);
}
}
void AS::listsourceline(SA_unpackedlines &srcline, bool coderequired,
bool &failure)
// List srcline, with option of listing generated code
{ listerrors(srcline.errors, failure);
if (coderequired) listcode(); else fprintf(Srce->lst, " ");
Srce->writetext(srcline.labfield, 9);
Srce->writetext(srcline.mnemonic, 9);
int width = strlen(srcline.address.term[0].name);
fputs(srcline.address.term[0].name, Srce->lst);
for (int i = 1; i < srcline.address.length; i++)
{ width += strlen(srcline.address.term[i].name) + 1;
putc(' ', Srce->lst);
fputs(srcline.address.term[i].name, Srce->lst);
}
if (width < 30) Srce->writetext(" ", 30 - width);
fprintf(Srce->lst, "%s\n", srcline.comment);
}
void AS::definemacro(SA_unpackedlines &srcline, bool &failure)
// Handle introduction of a macro (possibly nested)
{ MC_bytes opcode;
MH_macro macro;
bool declared = false;
int i = 0;
if (srcline.labelled) // name must be present
declared = true;
else
srcline.errors.incl(ASM_unlabelled);
if (!(srcline.address.length & 1)) // must be an odd number of terms
srcline.errors.incl(ASM_invalidaddress);
while (i < srcline.address.length) // check that formals are names
{ if (srcline.address.term[i].kind != SA_alphameric)
srcline.errors.incl(ASM_nonalpha);
i += 2; // bypass commas
}
listsourceline(srcline, nocodelisted, failure);
if (declared) Macro->newmacro(macro, srcline); // store header
do
{ Parser->parse(srcline); // next line of macro text
opcode = bytevalue(srcline.mnemonic);
if (opcode == AS_mac) // nested macro?
definemacro(srcline, failure); // recursion handles it
else
{ listsourceline(srcline, nocodelisted, failure);
if (declared && opcode != AS_end && srcline.errors.isempty())
Macro->storeline(macro, srcline); // add to macro text
}
} while (opcode != AS_end);
}
void AS::assembleline(SA_unpackedlines &srcline, bool &failure)
// Assemble single srcline
{ if (!include) { include = true; return; } // conditional assembly
bool badaddress, found, undefined;
MH_macro macro;
int formal;
Macro->checkmacro(srcline.mnemonic, macro, found, formal);
if (found) // expand macro and exit
{ if (srcline.labelled) Table->enter(srcline.labfield, location);
if (formal != srcline.address.length) // number of params okay?
srcline.errors.incl(ASM_mismatched);
listsourceline(srcline, nocodelisted, failure);
if (srcline.errors.isempty()) // okay to expand?
Macro->expand(macro, srcline.address, this, failure);
return;
}
badaddress = false;
objline.location = location; objline.address = 0;
objline.opcode = bytevalue(srcline.mnemonic);
if (objline.opcode == AS_err) // check various constraints
srcline.errors.incl(ASM_invalidcode);
else if (objline.opcode > AS_mac ||
objline.opcode > MC_hlt && objline.opcode < AS_err)
{ if (srcline.address.length == 0) srcline.errors.incl(ASM_noaddress); }
else if (objline.opcode != AS_mac && srcline.address.length != 0)
srcline.errors.incl(ASM_hasaddress);
if (objline.opcode >= AS_err && objline.opcode <= AS_dc)
{ switch (objline.opcode) // directives
{ case AS_beg:
location = 0;
break;
case AS_org:
evaluate(srcline.address, location, undefined, badaddress);
if (undefined) srcline.errors.incl(ASM_undefinedlabel);
objline.location = location;
break;
case AS_ds:
if (srcline.labelled) Table->enter(srcline.labfield, location);
evaluate(srcline.address, objline.address, undefined, badaddress);
if (undefined) srcline.errors.incl(ASM_undefinedlabel);
location = (location + objline.address) % 256;
break;
case AS_nul:
case AS_err:
if (srcline.labelled) Table->enter(srcline.labfield, location);
break;
case AS_equ:
evaluate(srcline.address, objline.address, undefined, badaddress);
if (srcline.labelled)
Table->enter(srcline.labfield, objline.address);
else
srcline.errors.incl(ASM_unlabelled);
if (undefined) srcline.errors.incl(ASM_undefinedlabel);
break;
case AS_dc:
if (srcline.labelled) Table->enter(srcline.labfield, location);
evaluate(srcline.address, objline.address, undefined, badaddress);
Machine->mem[location] = objline.address;
location = (location + 1) % 256;
break;
case AS_if:
evaluate(srcline.address, objline.address, undefined, badaddress);
if (undefined) srcline.errors.incl(ASM_undefinedlabel);
include = (objline.address != 0);
break;
case AS_mac:
definemacro(srcline, failure);
break;
case AS_end:
assembling = false;
break;
}
}
else // machine ops
{ if (srcline.labelled) Table->enter(srcline.labfield, location);
Machine->mem[location] = objline.opcode;
if (objline.opcode > MC_hlt) // TwoByteOps
{ location = (location + 1) % 256;
evaluate(srcline.address, objline.address, undefined, badaddress);
Machine->mem[location] = objline.address;
}
location = (location + 1) % 256; // bump location counter
}
if (badaddress) srcline.errors.incl(ASM_invalidaddress);
if (objline.opcode != AS_mac) listsourceline(srcline, codelisted, failure);
}
void AS::firstpass(bool &errors)
// Make first and only pass over source code
{ SA_unpackedlines srcline;
location = 0; assembling = true; include = true; errors = false;
while (assembling)
{ Parser->parse(srcline); assembleline(srcline, errors); }
Table->printsymboltable(errors);
if (!errors) Table->outstandingreferences(Machine->mem, backpatch);
}
void AS::assemble(bool &errors)
{ printf("Assembling ...\n");
fprintf(Srce->lst, "(One Pass Macro Assembler)\n\n");
firstpass(errors);
Machine->listcode();
}
AS::AS(char *sourcename, char *listname, char *version, MC *M)
{ Machine = M;
Srce = new SH(sourcename, listname, version);
Lex = new LA(Srce);
Parser = new SA(Lex);
Table = new ST(Srce);
Macro = new MH();
// enter opcodes and mnemonics in ALPHABETIC order
// done this way for ease of modification later
opcodes = 0; // bogus one for erroneous data
enter("Error ", AS_err); // for lines with no opcode
enter(" ", AS_nul); enter("ACI", MC_aci); enter("ACX", MC_acx);
enter("ADC", MC_adc); enter("ADD", MC_add); enter("ADI", MC_adi);
enter("ADX", MC_adx); enter("ANA", MC_ana); enter("ANI", MC_ani);
enter("ANX", MC_anx); enter("BCC", MC_bcc); enter("BCS", MC_bcs);
enter("BEG", AS_beg); enter("BNG", MC_bng); enter("BNZ", MC_bnz);
enter("BPZ", MC_bpz); enter("BRN", MC_brn); enter("BZE", MC_bze);
enter("CLA", MC_cla); enter("CLC", MC_clc); enter("CLX", MC_clx);
enter("CMC", MC_cmc); enter("CMP", MC_cmp); enter("CPI", MC_cpi);
enter("CPX", MC_cpx); enter("DC", AS_dc); enter("DEC", MC_dec);
enter("DEX", MC_dex); enter("DS", AS_ds); enter("END", AS_end);
enter("EQU", AS_equ); enter("HLT", MC_hlt); enter("IF", AS_if);
enter("INA", MC_ina); enter("INB", MC_inb); enter("INC", MC_inc);
enter("INH", MC_inh); enter("INI", MC_ini); enter("INX", MC_inx);
enter("JSR", MC_jsr); enter("LDA", MC_lda); enter("LDI", MC_ldi);
enter("LDX", MC_ldx); enter("LSI", MC_lsi); enter("LSP", MC_lsp);
enter("MAC", AS_mac); enter("NOP", MC_nop); enter("ORA", MC_ora);
enter("ORG", AS_org); enter("ORI", MC_ori); enter("ORX", MC_orx);
enter("OTA", MC_ota); enter("OTB", MC_otb); enter("OTC", MC_otc);
enter("OTH", MC_oth); enter("OTI", MC_oti); enter("POP", MC_pop);
enter("PSH", MC_psh); enter("RET", MC_ret); enter("SBC", MC_sbc);
enter("SBI", MC_sbi); enter("SBX", MC_sbx); enter("SCI", MC_sci);
enter("SCX", MC_scx); enter("SHL", MC_shl); enter("SHR", MC_shr);
enter("STA", MC_sta); enter("STX", MC_stx); enter("SUB", MC_sub);
enter("TAX", MC_tax);
}
----- mc.h --------------------------------------------------------------------
// Definition of simple single-accumulator machine and simple emulator
// P.D. Terry, Rhodes University, 1996
#ifndef MC_H
#define MC_H
#include "misc.h"
// machine instructions - order important
enum MC_opcodes {
MC_nop, MC_cla, MC_clc, MC_clx, MC_cmc, MC_inc, MC_dec, MC_inx, MC_dex,
MC_tax, MC_ini, MC_inh, MC_inb, MC_ina, MC_oti, MC_otc, MC_oth, MC_otb,
MC_ota, MC_psh, MC_pop, MC_shl, MC_shr, MC_ret, MC_hlt, MC_lda, MC_ldx,
MC_ldi, MC_lsp, MC_lsi, MC_sta, MC_stx, MC_add, MC_adx, MC_adi, MC_adc,
MC_acx, MC_aci, MC_sub, MC_sbx, MC_sbi, MC_sbc, MC_scx, MC_sci, MC_cmp,
MC_cpx, MC_cpi, MC_ana, MC_anx, MC_ani, MC_ora, MC_orx, MC_ori, MC_brn,
MC_bze, MC_bnz, MC_bpz, MC_bng, MC_bcc, MC_bcs, MC_jsr, MC_bad = 255 };
typedef enum { running, finished, nodata, baddata, badop } status;
typedef unsigned char MC_bytes;
class MC {
public:
MC_bytes mem[256]; // virtual machine memory
void listcode(void);
// Lists the 256 bytes stored in mem on requested output file
void emulator(MC_bytes initpc, FILE *data, FILE *results, bool tracing);
// Emulates action of the instructions stored in mem, with program counter
// initialized to initpc. data and results are used for I/O.
// Tracing at the code level may be requested
void interpret(void);
// Interactively opens data and results files, and requests entry point.
// Then interprets instructions stored in MC_mem
MC_bytes opcode(char *str);
// Maps str to opcode, or to MC_bad (0FFH) if no match can be found
MC();
// Initializes accumulator machine
private:
struct processor {
MC_bytes a; // Accumulator
MC_bytes sp; // Stack pointer
MC_bytes x; // Index register
MC_bytes ir; // Instruction register
MC_bytes pc; // Program count
bool z, p, c; // Condition flags
};
processor cpu;
status ps;
char *mnemonics[256];
void trace(FILE *results, MC_bytes pcnow);
void postmortem(FILE *results, MC_bytes pcnow);
void setflags(MC_bytes MC_register);
MC_bytes index(void);
};
#endif /*MC_H*/
----- mc.cpp ------------------------------------------------------------------
// Definition of simple single-accumulator machine and simple emulator
// P.D. Terry, Rhodes University, 1996
#include "misc.h"
#include "mc.h"
// set break-in character as CTRL-A (cannot easily use \033 on MS-DOS)
const int ESC = 1;
inline void increment(MC_bytes &x)
// Increment with folding at 256
{ x = (x + 257) % 256; }
inline void decrement(MC_bytes &x)
// Decrement with folding at 256
{ x = (x + 255) % 256; }
MC_bytes MC::opcode(char *str)
// Simple linear search suffices for illustration
{ for (int i = 0; str[i]; i++) str[i] = toupper(str[i]);
MC_bytes l = MC_nop;
while (l <= MC_jsr && strcmp(str, mnemonics[l])) l++;
if (l <= MC_jsr) return l; else return MC_bad;
}
void MC::listcode(void)
// Simply print all 256 bytes in 16 rows
{ MC_bytes nextbyte = 0;
char filename[256];
printf("Listing code ... \n");
printf("Listing file [NUL] ? ");
gets(filename);
if (*filename == '\0') return;
FILE *listfile = fopen(filename, "w");
if (listfile == NULL) listfile = stdout;
putc('\n', listfile);
for (int i = 1; i <= 16; i++)
{ for (int j = 1; j <= 16; j++)
{ fprintf(listfile, "%4d", mem[nextbyte]); increment(nextbyte); }
putc('\n', listfile);
}
if (listfile != stdout) fclose(listfile);
}
void MC::trace(FILE *results, MC_bytes pcnow)
// Simple trace facility for run time debugging
{ fprintf(results, " PC = %02X A = %02X ", pcnow, cpu.a);
fprintf(results, " X = %02X SP = %02X ", cpu.x, cpu.sp);
fprintf(results, " Z = %d P = %d C = %d", cpu.z, cpu.p, cpu.c);
fprintf(results, " OPCODE = %02X (%s)\n", cpu.ir, mnemonics[cpu.ir]);
}
void MC::postmortem(FILE *results, MC_bytes pcnow)
// Report run time error and position
{ switch (ps)
{ case badop: fprintf(results, "Illegal opcode"); break;
case nodata: fprintf(results, "No more data"); break;
case baddata: fprintf(results, "Invalid data"); break;
}
fprintf(results, " at %d\n", pcnow);
trace(results, pcnow);
printf("\nPress RETURN to continue\n");
scanf("%*[^\n]"); getchar();
listcode();
}
inline void MC::setflags(MC_bytes MC_register)
// Set P and Z flags according to contents of register
{ cpu.z = (MC_register == 0); cpu.p = (MC_register <= 127); }
inline MC_bytes MC::index(void)
// Get indexed address with folding at 256
{ return ((mem[cpu.pc] + cpu.x) % 256); }
void readchar(FILE *data, char &ch, status &ps)
// Read ch and check for break-in and other awkward values
{ if (feof(data)) { ps = nodata; ch = ' '; return; }
ch = getc(data);
if (ch == ESC) ps = finished;
if (ch < ' ' || feof(data)) ch = ' ';
}
int hexdigit(char ch)
// Convert CH to equivalent value
{ if (ch >= 'a' && ch <= 'e') return(ch + 10 - 'a');
if (ch >= 'A' && ch <= 'E') return(ch + 10 - 'A');
if (isdigit(ch)) return(ch - '0');
else return(0);
}
int getnumber(FILE *data, int base, status &ps)
// Read number in required base
{ bool negative = false;
char ch;
int num = 0;
do
{ readchar(data, ch, ps);
} while (!(ch > ' ' || feof(data) || ps != running));
if (ps == running)
{ if (feof(data))
ps = nodata;
else
{ if (ch == '-') { negative = true; readchar(data, ch, ps); }
else if (ch == '+') readchar(data, ch, ps);
if (!isxdigit(ch))
ps = baddata;
else
{ while (isxdigit(ch) && ps == running)
{ if (hexdigit(ch) < base && num <= (maxint - hexdigit(ch)) / base)
num = base * num + hexdigit(ch);
else
ps = baddata;
readchar(data, ch, ps);
}
}
}
if (negative) num = -num;
if (num > 0)
return num % 256;
else
return (256 - abs(num) % 256) % 256;
}
return 0;
}
void MC::emulator(MC_bytes initpc, FILE *data, FILE *results, bool tracing)
{ MC_bytes pcnow; // Old program count
MC_bytes carry; // Value of carry bit
cpu.z = false; cpu.p = false; cpu.c = false; // initialize flags
cpu.a = 0; cpu.x = 0; cpu.sp = 0; // initialize registers
cpu.pc = initpc; // initialize program counter
ps = running;
do
{ cpu.ir = mem[cpu.pc]; // fetch
pcnow = cpu.pc; // record for use in tracing/postmortem
increment(cpu.pc); // and bump in anticipation
if (tracing) trace(results, pcnow);
switch (cpu.ir) // execute
{ case MC_nop:
break;
case MC_cla:
cpu.a = 0; break;
case MC_clc:
cpu.c = false; break;
case MC_clx:
cpu.x = 0; break;
case MC_cmc:
cpu.c = !cpu.c; break;
case MC_inc:
increment(cpu.a); setflags(cpu.a); break;
case MC_dec:
decrement(cpu.a); setflags(cpu.a); break;
case MC_inx:
increment(cpu.x); setflags(cpu.x); break;
case MC_dex:
decrement(cpu.x); setflags(cpu.x); break;
case MC_tax:
cpu.x = cpu.a; break;
case MC_ini:
cpu.a = getnumber(data, 10, ps); setflags(cpu.a); break;
case MC_inb:
cpu.a = getnumber(data, 2, ps); setflags(cpu.a); break;
case MC_inh:
cpu.a = getnumber(data, 16, ps); setflags(cpu.a); break;
case MC_ina:
char ascii;
readchar(data, ascii, ps);
if (feof(data)) ps = nodata;
else { cpu.a = ascii; setflags(cpu.a); }
break;
case MC_oti:
if (cpu.a < 128)
fprintf(results, "%d ", cpu.a);
else
fprintf(results, "%d ", cpu.a - 256);
if (tracing) putc('\n', results);
break;
case MC_oth:
fprintf(results, "%02X ", cpu.a);
if (tracing) putc('\n', results);
break;
case MC_otc:
fprintf(results, "%d ", cpu.a);
if (tracing) putc('\n', results);
break;
case MC_ota:
putc(cpu.a, results);
if (tracing) putc('\n', results);
break;
case MC_otb:
int bits[8];
MC_bytes number = cpu.a;
for (int loop = 0; loop <= 7; loop++)
{ bits[loop] = number % 2; number /= 2; }
for (loop = 7; loop >= 0; loop--)
fprintf(results, "%d", bits[loop]);
putc(' ', results);
if (tracing) putc('\n', results);
break;
case MC_psh:
decrement(cpu.sp); mem[cpu.sp] = cpu.a; break;
case MC_pop:
cpu.a = mem[cpu.sp]; increment(cpu.sp); setflags(cpu.a); break;
case MC_shl:
cpu.c = (cpu.a * 2 > 255); cpu.a = cpu.a * 2 % 256;
setflags(cpu.a); break;
case MC_shr:
cpu.c = cpu.a & 1; cpu.a /= 2; setflags(cpu.a); break;
case MC_ret:
cpu.pc = mem[cpu.sp]; increment(cpu.sp); break;
case MC_hlt:
ps = finished; break;
case MC_lda:
cpu.a = mem[mem[cpu.pc]]; increment(cpu.pc); setflags(cpu.a); break;
case MC_ldx:
cpu.a = mem[index()]; increment(cpu.pc); setflags(cpu.a); break;
case MC_ldi:
cpu.a = mem[cpu.pc]; increment(cpu.pc); setflags(cpu.a); break;
case MC_lsp:
cpu.sp = mem[mem[cpu.pc]]; increment(cpu.pc); break;
case MC_lsi:
cpu.sp = mem[cpu.pc]; increment(cpu.pc); break;
case MC_sta:
mem[mem[cpu.pc]] = cpu.a; increment(cpu.pc); break;
case MC_stx:
mem[index()] = cpu.a; increment(cpu.pc); break;
case MC_add:
cpu.c = (cpu.a + mem[mem[cpu.pc]] > 255);
cpu.a = (cpu.a + mem[mem[cpu.pc]]) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_adx:
cpu.c = (cpu.a + mem[index()] > 255);
cpu.a = (cpu.a + mem[index()]) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_adi:
cpu.c = (cpu.a + mem[cpu.pc] > 255);
cpu.a = (cpu.a + mem[cpu.pc]) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_adc:
carry = cpu.c;
cpu.c = (cpu.a + mem[mem[cpu.pc]] + carry > 255);
cpu.a = (cpu.a + mem[mem[cpu.pc]] + carry) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_acx:
carry = cpu.c;
cpu.c = (cpu.a + mem[index()] + carry > 255);
cpu.a = (cpu.a + mem[index()] + carry) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_aci:
carry = cpu.c;
cpu.c = (cpu.a + mem[cpu.pc] + carry > 255);
cpu.a = (cpu.a + mem[cpu.pc] + carry) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_sub:
cpu.c = (cpu.a < mem[mem[cpu.pc]]);
cpu.a = (cpu.a - mem[mem[cpu.pc]] + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_sbx:
cpu.c = (cpu.a < mem[index()]);
cpu.a = (cpu.a - mem[index()] + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_sbi:
cpu.c = (cpu.a < mem[cpu.pc]);
cpu.a = (cpu.a - mem[cpu.pc] + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_sbc:
carry = cpu.c;
cpu.c = (cpu.a < mem[mem[cpu.pc]] + carry);
cpu.a = (cpu.a - mem[mem[cpu.pc]] - carry + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_scx:
carry = cpu.c;
cpu.c = (cpu.a < mem[index()] + carry);
cpu.a = (cpu.a - mem[index()] - carry + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_sci:
carry = cpu.c;
cpu.c = (cpu.a < mem[cpu.pc] + carry);
cpu.a = (cpu.a - mem[cpu.pc] - carry + 256) % 256;
increment(cpu.pc); setflags(cpu.a); break;
case MC_cmp:
cpu.c = (cpu.a < mem[mem[cpu.pc]]);
setflags((cpu.a - mem[mem[cpu.pc]] + 256) % 256);
increment(cpu.pc); break;
case MC_cpx:
cpu.c = (cpu.a < mem[index()]);
setflags((cpu.a - mem[index()] + 256) % 256);
increment(cpu.pc); break;
case MC_cpi:
cpu.c = (cpu.a < mem[cpu.pc]);
setflags((cpu.a - mem[cpu.pc] + 256) % 256);
increment(cpu.pc); break;
case MC_ana:
cpu.a &= mem[mem[cpu.pc]];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_anx:
cpu.a &= mem[index()];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_ani:
cpu.a &= mem[cpu.pc];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_ora:
cpu.a |= mem[mem[cpu.pc]];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_orx:
cpu.a |= mem[index()];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_ori:
cpu.a |= mem[cpu.pc];
increment(cpu.pc); setflags(cpu.a); cpu.c = false; break;
case MC_brn:
cpu.pc = mem[cpu.pc]; break;
case MC_bze:
if (cpu.z) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_bnz:
if (!cpu.z) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_bpz:
if (cpu.p) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_bng:
if (!cpu.p) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_bcs:
if (cpu.c) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_bcc:
if (!cpu.c) cpu.pc = mem[cpu.pc]; else increment(cpu.pc); break;
case MC_jsr:
decrement(cpu.sp);
mem[cpu.sp] = (cpu.pc + 1) % 256; // push return address
cpu.pc = mem[cpu.pc]; break;
default:
ps = badop; break;
}
} while (ps == running);
if (ps != finished) postmortem(results, pcnow);
}
void MC::interpret(void)
{ char filename[256];
FILE *data, *results;
bool tracing;
int entry;
printf("\nTrace execution (y/N/q)? ");
char reply = getchar(); scanf("%*[^\n]"); getchar();
if (toupper(reply) != 'Q')
{ tracing = toupper(reply) == 'Y';
printf("\nData file [STDIN] ? "); gets(filename);
if (filename[0] == '\0') data = NULL;
else data = fopen(filename, "r");
if (data == NULL)
{ printf("taking data from stdin\n"); data = stdin; }
printf("\nResults file [STDOUT] ? "); gets(filename);
if (filename[0] == '\0') results = NULL;
else results = fopen(filename, "w");
if (results == NULL)
{ printf("sending results to stdout\n"); results = stdout; }
printf("Entry point? ");
if (scanf("%d%*[^\n]", &entry) != 1) entry = 0; getchar();
emulator(entry % 256, data, results, tracing);
if (results != stdout) fclose(results);
if (data != stdin) fclose(data);
}
}
MC::MC()
{ for (int i = 0; i <= 255; i++) mem[i] = MC_bad;
// Initialize mnemonic table
for (i = 0; i <= 255; i++) mnemonics[i] = "???";
mnemonics[MC_aci] = "ACI"; mnemonics[MC_acx] = "ACX";
mnemonics[MC_adc] = "ADC"; mnemonics[MC_add] = "ADD";
mnemonics[MC_adi] = "ADI"; mnemonics[MC_adx] = "ADX";
mnemonics[MC_ana] = "ANA"; mnemonics[MC_ani] = "ANI";
mnemonics[MC_anx] = "ANX"; mnemonics[MC_bcc] = "BCC";
mnemonics[MC_bcs] = "BCS"; mnemonics[MC_bng] = "BNG";
mnemonics[MC_bnz] = "BNZ"; mnemonics[MC_bpz] = "BPZ";
mnemonics[MC_brn] = "BRN"; mnemonics[MC_bze] = "BZE";
mnemonics[MC_cla] = "CLA"; mnemonics[MC_clc] = "CLC";
mnemonics[MC_clx] = "CLX"; mnemonics[MC_cmc] = "CMC";
mnemonics[MC_cmp] = "CMP"; mnemonics[MC_cpi] = "CPI";
mnemonics[MC_cpx] = "CPX"; mnemonics[MC_dec] = "DEC";
mnemonics[MC_dex] = "DEX"; mnemonics[MC_hlt] = "HLT";
mnemonics[MC_ina] = "INA"; mnemonics[MC_inb] = "INB";
mnemonics[MC_inc] = "INC"; mnemonics[MC_inh] = "INH";
mnemonics[MC_ini] = "INI"; mnemonics[MC_inx] = "INX";
mnemonics[MC_jsr] = "JSR"; mnemonics[MC_lda] = "LDA";
mnemonics[MC_ldi] = "LDI"; mnemonics[MC_ldx] = "LDX";
mnemonics[MC_lsi] = "LSI"; mnemonics[MC_lsp] = "LSP";
mnemonics[MC_nop] = "NOP"; mnemonics[MC_ora] = "ORA";
mnemonics[MC_ori] = "ORI"; mnemonics[MC_orx] = "ORX";
mnemonics[MC_ota] = "OTA"; mnemonics[MC_otb] = "OTB";
mnemonics[MC_otc] = "OTC"; mnemonics[MC_oth] = "OTH";
mnemonics[MC_oti] = "OTI"; mnemonics[MC_pop] = "POP";
mnemonics[MC_psh] = "PSH"; mnemonics[MC_ret] = "RET";
mnemonics[MC_sbc] = "SBC"; mnemonics[MC_sbi] = "SBI";
mnemonics[MC_sbx] = "SBX"; mnemonics[MC_sci] = "SCI";
mnemonics[MC_scx] = "SCX"; mnemonics[MC_shl] = "SHL";
mnemonics[MC_shr] = "SHR"; mnemonics[MC_sta] = "STA";
mnemonics[MC_stx] = "STX"; mnemonics[MC_sub] = "SUB";
mnemonics[MC_tax] = "TAX";
}