| git.druid.rocks | index | druid520 | jury | src/ | asm.c |
src/asm.c
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "isa.h"
#define LINE_LEN 1024
#define TOK_LEN 32
#define TOKS_MAX 64
#define LABELS_MAX 1024
#define FIXUPS_MAX 8192
/* how a value operand gets used */
#define VAL_IMM 1 /* one byte */
#define VAL_ADDR 2 /* two bytes, big endian, must be inside ram */
/* which part of a value: all of it, or <x / >x for the low/high byte */
#define PART_ALL 0
#define PART_LO 1
#define PART_HI 2
typedef struct {
char name[TOK_LEN];
unsigned int val;
} label;
typedef struct {
char name[TOK_LEN];
unsigned int at; /* where in out[] it goes */
int kind;
int part;
int line;
} fixup;
/* a "name;" label: its bytes, recorded for the .vars file */
typedef struct {
char name[TOK_LEN];
unsigned int at;
unsigned int len;
int line;
} var;
unsigned char out[RAM_SIZE];
unsigned int nout = 0;
label labels[LABELS_MAX];
int nlabels = 0;
fixup fixups[FIXUPS_MAX];
int nfixups = 0;
var vars[VARS_MAX];
int nvars = 0;
int lineno = 0;
void fail(const char* fmt, ...);
int hexval(int c);
int isregname(const char* s);
int islabelname(const char* s);
int labelfind(const char* name);
void labeldef(const char* name);
void vardef(const char* name);
void varmore(void);
void emit(unsigned int v);
int parsereg(const char* s);
void parsepair(const char* s, int* hi, int* lo);
void putval(unsigned int at, unsigned int v, int kind, int part, const char* what);
void parseval(const char* s, int kind);
int splitline(char* line, char toks[][TOK_LEN]);
void parseins(char toks[][TOK_LEN], int ntok, int mode, int op, int nib);
void parsestream(FILE* in);
void resolve(void);
int writevars(const char* binfile);
int main(int argc, char** argv);
void
fail(const char* fmt, ...)
{
va_list ap;
fprintf(stderr, "err: ");
if(lineno > 0)
{
fprintf(stderr, "line %d: ", lineno);
}
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
int
hexval(int c)
{
if(c >= '0' && c <= '9')
{
return c - '0';
}
return (toupper(c) - 'A') + 10;
}
int
isregname(const char* s)
{
return (s[0] == 'r' || s[0] == 'R') && isdigit((unsigned char)s[1]) && s[2] == '\0';
}
int
islabelname(const char* s)
{
int i;
if(isalpha((unsigned char)s[0]) == 0 && s[0] != '_')
{
return 0;
}
for(i = 1; s[i] != '\0'; i = i + 1)
{
if(isalnum((unsigned char)s[i]) == 0 && s[i] != '_')
{
return 0;
}
}
return isregname(s) == 0;
}
int
labelfind(const char* name)
{
int i;
for(i = 0; i < nlabels; i = i + 1)
{
if(strcmp(labels[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void
labeldef(const char* name)
{
if(islabelname(name) == 0)
{
fail("bad label name '%s'.", name);
}
if(labelfind(name) >= 0)
{
fail("duplicate label '%s'.", name);
}
if(nlabels == LABELS_MAX)
{
fail("too many labels.");
}
strcpy(labels[nlabels].name, name);
labels[nlabels].val = nout;
nlabels = nlabels + 1;
}
/* name was just defined as a label, it starts a variable too */
void
vardef(const char* name)
{
if(nvars == VARS_MAX)
{
fail("too many variables.");
}
strcpy(vars[nvars].name, name);
vars[nvars].at = nout;
vars[nvars].len = 0;
vars[nvars].line = lineno;
nvars = nvars + 1;
}
/* a lone ";" carries on the variable right before it */
void
varmore(void)
{
if(nvars == 0 || vars[nvars - 1].at + vars[nvars - 1].len != nout)
{
fail("';' with no variable right before it to carry on.");
}
}
void
emit(unsigned int v)
{
if(nout == RAM_SIZE)
{
fail("program is bigger than ram (%d bytes).", RAM_SIZE);
}
out[nout] = v & 0xFF;
nout = nout + 1;
}
int
parsereg(const char* s)
{
if(isregname(s) == 0 || s[1] - '0' >= NREGS)
{
fail("bad register '%s', want r0-r%d.", s, NREGS - 1);
}
return s[1] - '0';
}
/* "r1:r2" */
void
parsepair(const char* s, int* hi, int* lo)
{
char h[TOK_LEN];
const char* c = strchr(s, ':');
if(c == NULL || c - s >= TOK_LEN)
{
fail("bad register pair '%s', want like r1:r2.", s);
}
memcpy(h, s, c - s);
h[c - s] = '\0';
*hi = parsereg(h);
*lo = parsereg(c + 1);
}
/* number or label value v, filled in at out[at] */
void
putval(unsigned int at, unsigned int v, int kind, int part, const char* what)
{
if(part == PART_LO)
{
v = v & 0xFF;
}
if(part == PART_HI)
{
v = (v >> 8) & 0xFF;
}
if(kind == VAL_IMM)
{
if(v > 0xFF)
{
fail("'%s' is %u, doesnt fit in a byte (try <%s or >%s).", what, v, what, what);
}
out[at] = v;
return;
}
if(v >= RAM_SIZE)
{
fail("address '%s' is %u, past the end of ram.", what, v);
}
out[at] = (v >> 8) & 0xFF;
out[at + 1] = v & 0xFF;
}
/* decimal, 0x hex, or a label, optionally prefixed < (low byte) or >
* (high byte). emits the operand bytes, label values get patched in by
* resolve() once every label is known. */
void
parseval(const char* s, int kind)
{
const char* p = s;
int part = PART_ALL;
unsigned long v = 0;
unsigned int at = nout;
int base = 10;
if(*p == '<' || *p == '>')
{
if(kind != VAL_IMM)
{
fail("'%s': < and > only work on byte operands.", s);
}
part = *p == '<' ? PART_LO : PART_HI;
p = p + 1;
}
emit(0);
if(kind == VAL_ADDR)
{
emit(0);
}
if(isregname(p) != 0)
{
fail("wanted a value, got register '%s'.", p);
}
if(islabelname(p) != 0)
{
if(nfixups == FIXUPS_MAX)
{
fail("too many label references.");
}
strcpy(fixups[nfixups].name, p);
fixups[nfixups].at = at;
fixups[nfixups].kind = kind;
fixups[nfixups].part = part;
fixups[nfixups].line = lineno;
nfixups = nfixups + 1;
return;
}
if(p[0] == '0' && (p[1] == 'x' || p[1] == 'X'))
{
base = 16;
p = p + 2;
}
if(*p == '\0')
{
fail("bad operand '%s'.", s);
}
for(; *p != '\0'; p = p + 1)
{
if((base == 16 && isxdigit((unsigned char)*p) == 0) ||
(base == 10 && isdigit((unsigned char)*p) == 0))
{
fail("bad operand '%s'.", s);
}
v = v * base + hexval((unsigned char)*p);
if(v > 0xFFFF)
{
fail("operand '%s' out of range.", s);
}
}
putval(at, (unsigned int)v, kind, part, s);
}
/* whitespace and commas both separate tokens */
int
splitline(char* line, char toks[][TOK_LEN])
{
int n = 0;
int j;
char* p = line;
for(;;)
{
while(*p != '\0' && (isspace((unsigned char)*p) || *p == ','))
{
p = p + 1;
}
if(*p == '\0')
{
return n;
}
if(n == TOKS_MAX)
{
fail("too many tokens.");
}
j = 0;
while(*p != '\0' && isspace((unsigned char)*p) == 0 && *p != ',')
{
if(j == TOK_LEN - 1)
{
fail("token too long.");
}
toks[n][j] = *p;
j = j + 1;
p = p + 1;
}
toks[n][j] = '\0';
n = n + 1;
}
}
/* one instruction, toks[0] is the mnemonic. operands are source first,
* destination last, and have to match the order isafmt prints them in. */
void
parseins(char toks[][TOK_LEN], int ntok, int mode, int op, int nib)
{
/* operand count, indexed by format, same order as the enum in isa.h */
static const int NARGS[] = {0, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 0};
const opdef* d = &MODES[mode][op];
int x = 0;
int y = 0;
if(ntok - 1 != NARGS[d->fmt])
{
fail("%s takes %d operand(s), got %d.", toks[0], NARGS[d->fmt], ntok - 1);
}
if(mode != 0)
{
emit(0xC0 | mode);
}
switch(d->fmt)
{
case FMT_NONE:
emit(op << 4);
break;
case FMT_R:
emit((op << 4) | parsereg(toks[1]));
break;
case FMT_IR:
emit((op << 4) | parsereg(toks[2]));
parseval(toks[1], VAL_IMM);
break;
case FMT_AR:
emit((op << 4) | parsereg(toks[2]));
parseval(toks[1], VAL_ADDR);
break;
case FMT_RA:
emit((op << 4) | parsereg(toks[1]));
parseval(toks[2], VAL_ADDR);
break;
case FMT_CA:
emit((op << 4) | nib);
parseval(toks[1], VAL_ADDR);
break;
case FMT_A:
emit(op << 4);
parseval(toks[1], VAL_ADDR);
break;
case FMT_P:
parsepair(toks[1], &x, &y);
emit(op << 4);
emit((x << 4) | y);
break;
case FMT_PR:
emit((op << 4) | parsereg(toks[2]));
parsepair(toks[1], &x, &y);
emit((x << 4) | y);
break;
case FMT_RP:
emit((op << 4) | parsereg(toks[1]));
parsepair(toks[2], &x, &y);
emit((x << 4) | y);
break;
case FMT_RR:
/* written "s, d", encoded d:s */
y = parsereg(toks[1]);
x = parsereg(toks[2]);
emit(op << 4);
emit((x << 4) | y);
break;
}
}
void
parsestream(FILE* in)
{
char line[LINE_LEN];
static char toks[TOKS_MAX][TOK_LEN];
int ntok;
int t;
int len;
int mode;
int op;
int nib;
int isvar;
char* h;
while(fgets(line, sizeof(line), in) != NULL)
{
lineno = lineno + 1;
if(strchr(line, '\n') == NULL && !feof(in))
{
fail("line too long.");
}
h = strchr(line, '#');
if(h != NULL)
{
*h = '\0';
}
ntok = splitline(line, toks);
/* leading "name:" tokens are labels. "name;" is a label that
* starts a variable, a lone ";" carries on the last one - either
* way the rest of the line is the variable's bytes. */
t = 0;
isvar = 0;
while(t < ntok && isvar == 0)
{
len = strlen(toks[t]);
if(strcmp(toks[t], ";") == 0)
{
varmore();
isvar = 1;
}
else if(len >= 2 && (toks[t][len - 1] == ':' || toks[t][len - 1] == ';'))
{
isvar = toks[t][len - 1] == ';';
toks[t][len - 1] = '\0';
labeldef(toks[t]);
if(isvar != 0)
{
vardef(toks[t]);
}
}
else
{
break;
}
t = t + 1;
}
if(isvar != 0)
{
/* same values as any byte operand: decimal or 0x hex, never
* bare two digit hex, "62" here is sixty two. */
for(; t < ntok; t = t + 1)
{
parseval(toks[t], VAL_IMM);
}
vars[nvars - 1].len = nout - vars[nvars - 1].at;
continue;
}
if(t == ntok)
{
continue;
}
if(isafind(toks[t], &mode, &op, &nib) != 0)
{
parseins(toks + t, ntok - t, mode, op, nib);
continue;
}
if(sameci(toks[t], "MODE") != 0)
{
fail("dont write MODE, asm puts it in front of extended ops for you.");
}
/* otherwise the whole line is raw bytes */
for(; t < ntok; t = t + 1)
{
if(strlen(toks[t]) != 2 ||
isxdigit((unsigned char)toks[t][0]) == 0 ||
isxdigit((unsigned char)toks[t][1]) == 0)
{
fail("bad token '%s'.", toks[t]);
}
emit((hexval((unsigned char)toks[t][0]) << 4) | hexval((unsigned char)toks[t][1]));
}
}
for(t = 0; t < nvars; t = t + 1)
{
if(vars[t].len == 0)
{
lineno = vars[t].line;
fail("variable '%s' has no bytes.", vars[t].name);
}
}
lineno = 0;
}
void
resolve(void)
{
int i;
int li;
for(i = 0; i < nfixups; i = i + 1)
{
lineno = fixups[i].line;
li = labelfind(fixups[i].name);
if(li < 0)
{
fail("undefined label '%s'.", fixups[i].name);
}
putval(fixups[i].at, labels[li].val, fixups[i].kind, fixups[i].part, fixups[i].name);
}
lineno = 0;
}
/* binfile + ".vars", or no file at all if there are no variables, so
* a stale one from an older build cant describe the wrong binary. */
int
writevars(const char* binfile)
{
char* name;
FILE* f;
int i;
name = malloc(strlen(binfile) + strlen(VARS_EXT) + 1);
if(name == NULL)
{
fprintf(stderr, "err: out of memory.\n");
return 1;
}
strcpy(name, binfile);
strcat(name, VARS_EXT);
if(nvars == 0)
{
remove(name);
free(name);
return 0;
}
f = fopen(name, "wb");
if(f == NULL)
{
fprintf(stderr, "err: cant create %s.\n", name);
free(name);
return 1;
}
for(i = 0; i < nvars; i = i + 1)
{
fprintf(f, "%s %04x %04x\n", vars[i].name, vars[i].at, vars[i].len);
}
fclose(f);
printf("ok: wrote %s with %d variable(s).\n", name, nvars);
free(name);
return 0;
}
int
main(int argc, char** argv)
{
const char* infile = "src.s";
const char* outfile = "prog.bin";
FILE* in;
FILE* f;
if(argc > 3)
{
fprintf(stderr, "err: bad usage.\n");
fprintf(stderr, "usage: %s [in.s|-] [out.bin]\n", argv[0]);
return 1;
}
if(argc >= 2)
{
infile = argv[1];
}
if(argc >= 3)
{
outfile = argv[2];
}
if(strcmp(infile, "-") == 0)
{
in = stdin;
}
else
{
in = fopen(infile, "rb");
if(in == NULL)
{
fprintf(stderr, "err: cant open %s.\n", infile);
return 1;
}
}
parsestream(in);
if(in != stdin)
{
fclose(in);
}
resolve();
f = fopen(outfile, "wb");
if(f == NULL)
{
fprintf(stderr, "err: cant create %s.\n", outfile);
return 1;
}
fwrite(out, 1, nout, f);
fclose(f);
printf("ok: wrote %s with %u bytes.\n", outfile, nout);
return writevars(outfile);
}