mirror of
https://github.com/buserror/mii_emu.git
synced 2024-11-15 17:09:38 +00:00
504 lines
11 KiB
C
504 lines
11 KiB
C
|
/*
|
||
|
* mii_65c02_asm.c
|
||
|
*
|
||
|
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||
|
*
|
||
|
* SPDX-License-Identifier: MIT
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
#include "mii_65c02.h"
|
||
|
#include "mii_65c02_ops.h"
|
||
|
#include "mii_65c02_asm.h"
|
||
|
|
||
|
static char *
|
||
|
_mii_extract_token(
|
||
|
char *position,
|
||
|
char *dst,
|
||
|
int dst_len)
|
||
|
{
|
||
|
if (!position)
|
||
|
return NULL;
|
||
|
while (*position == ' ' || *position == '\t')
|
||
|
position++;
|
||
|
char *kw = strsep(&position, " \t");
|
||
|
if (kw)
|
||
|
strncpy(dst, kw, dst_len);
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
|
||
|
static char *
|
||
|
_mii_extract_name(
|
||
|
char *src,
|
||
|
char *dst,
|
||
|
int dst_len)
|
||
|
{
|
||
|
char *end = src;
|
||
|
while (isalnum(*end) || *end == '_')
|
||
|
*dst++ = *end++;
|
||
|
*dst = 0;
|
||
|
return end;
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
_mii_extract_value_or_name(
|
||
|
mii_cpu_asm_line_t *l,
|
||
|
char *src)
|
||
|
{
|
||
|
static const char * const hex = "0123456789abcdef";
|
||
|
char *end = src;
|
||
|
l->op_value = 0;
|
||
|
if (*end == '<') {
|
||
|
l->op_low = 1;
|
||
|
end++;
|
||
|
} else if (*end == '>') {
|
||
|
l->op_high = 1;
|
||
|
end++;
|
||
|
}
|
||
|
if (*end == '$') {
|
||
|
end++;
|
||
|
while (isxdigit(*end))
|
||
|
l->op_value = (l->op_value << 4) +
|
||
|
(strchr(hex, tolower(*end++)) - hex);
|
||
|
} else if (isdigit(*end)) {
|
||
|
while (isdigit(*end))
|
||
|
l->op_value = (l->op_value << 4) +
|
||
|
(strchr(hex, tolower(*end++)) - hex);
|
||
|
} else {
|
||
|
end = _mii_extract_name(end, l->op_name, sizeof(l->op_name));
|
||
|
l->label_resolved = 0;
|
||
|
}
|
||
|
return end;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
_mii_resolve_symbol(
|
||
|
mii_cpu_asm_program_t *p,
|
||
|
mii_cpu_asm_line_t *l,
|
||
|
int optional)
|
||
|
{
|
||
|
if (!l->op_name[0] || l->label_resolved)
|
||
|
return 1;
|
||
|
mii_cpu_asm_line_t *l2 = p->sym;
|
||
|
while (l2) {
|
||
|
if (!strcasecmp(l->op_name, l2->label)) {
|
||
|
l->op_value = l2->op_value;
|
||
|
l->label_resolved = 1;
|
||
|
goto found;
|
||
|
}
|
||
|
l2 = l2->sym_next;
|
||
|
}
|
||
|
if (!optional)
|
||
|
printf("%s symbol %s not found\n", __func__, l->op_name);
|
||
|
return 0;
|
||
|
found:
|
||
|
if (l->op_low)
|
||
|
l->op_value &= 0xff;
|
||
|
else if (l->op_high)
|
||
|
l->op_value >>= 8;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
mii_cpu_asm_load(
|
||
|
mii_cpu_asm_program_t *p,
|
||
|
const char *prog)
|
||
|
{
|
||
|
const char *current = prog;
|
||
|
int line_count = 0;
|
||
|
do {
|
||
|
const char *lend = strchr(current, '\n');
|
||
|
int len = lend ? (int)(lend - current) : (int)strlen(current);
|
||
|
|
||
|
mii_cpu_asm_line_t *l = calloc(1, sizeof(mii_cpu_asm_line_t) + len + 1);
|
||
|
l->line_index = line_count++;
|
||
|
sprintf(l->line, "%.*s", len, current);
|
||
|
if (p->prog_tail) {
|
||
|
p->prog_tail->next = l;
|
||
|
p->prog_tail = l;
|
||
|
} else {
|
||
|
p->prog = p->prog_tail = l;
|
||
|
}
|
||
|
char *dup = strdup(l->line);
|
||
|
char * comment = strrchr(dup, ';');
|
||
|
if (comment)
|
||
|
*comment = 0;
|
||
|
if (!*dup) {
|
||
|
goto next_line;
|
||
|
}
|
||
|
char * position = dup;
|
||
|
if (position[0] != ' ' && position[0] != '\t') {
|
||
|
char *start = position;
|
||
|
char *kw = strsep(&position, " \t=");
|
||
|
if (kw) {
|
||
|
// strip spaces
|
||
|
char *ke = kw + strlen(kw);
|
||
|
while (ke > start && (ke[-1] <= ' ' || ke[-1] == ':'))
|
||
|
*--ke = 0;
|
||
|
strncpy(l->label, kw, sizeof(l->label)-1);
|
||
|
}
|
||
|
}
|
||
|
position = _mii_extract_token(position, l->mnemonic, sizeof(l->mnemonic));
|
||
|
|
||
|
if (!strcmp(l->mnemonic, ".db") || !strcmp(l->mnemonic, "byte")) {
|
||
|
/* remaining of the line is comma separated hex values OR symbols
|
||
|
* that can be resolved immediately */
|
||
|
char *kw = position;
|
||
|
while (*kw == ' ' || *kw == '\t') kw++;
|
||
|
char *cur = kw;
|
||
|
while ((cur = strsep(&kw, ",")) != NULL) {
|
||
|
while (*cur == ' ' || *cur == '\t') cur++;
|
||
|
char *ke = cur + strlen(cur);
|
||
|
while (ke > cur && (ke[-1] <= ' ' || ke[-1] == ':'))
|
||
|
*--ke = 0;
|
||
|
if (!*cur)
|
||
|
break;
|
||
|
_mii_extract_value_or_name(l, cur);
|
||
|
if (_mii_resolve_symbol(p, l, 0) == 0)
|
||
|
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
|
||
|
else
|
||
|
l->opcodes[l->opcode_count++] = l->op_value;
|
||
|
}
|
||
|
goto next_line;
|
||
|
}
|
||
|
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
|
||
|
if (l->operand[0] == '.' || l->operand[0] == '=') {
|
||
|
// a directive that has been not aligned on the first line, re-adjust
|
||
|
snprintf(l->label, sizeof(l->label), "%s", l->mnemonic);
|
||
|
snprintf(l->mnemonic, sizeof(l->mnemonic), "%s", l->operand);
|
||
|
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
|
||
|
}
|
||
|
if (l->mnemonic[0] == '=' ||
|
||
|
!strcasecmp(l->mnemonic, "equ") ||
|
||
|
!strcasecmp(l->mnemonic, ".equ")) {
|
||
|
l->symbol = 1;
|
||
|
if (p->sym_tail)
|
||
|
p->sym_tail->sym_next = l;
|
||
|
else
|
||
|
p->sym = l;
|
||
|
_mii_extract_value_or_name(l, l->operand);
|
||
|
if (_mii_resolve_symbol(p, l, 0) == 0)
|
||
|
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
|
||
|
}
|
||
|
// printf(">L:%s M:%s O:%s\n", l->label, l->mnemonic, l->operand);
|
||
|
next_line:
|
||
|
free(dup);
|
||
|
current = lend ? lend+1 : NULL;
|
||
|
} while (current);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static int
|
||
|
mii_cpu_opcode_has_mode(
|
||
|
const char *mnemonic,
|
||
|
int mode)
|
||
|
{
|
||
|
for (int i = 0; i < 256; i++) {
|
||
|
if (!strncasecmp(mnemonic, mii_cpu_op[i].name, 4)) {
|
||
|
if (mode == mii_cpu_op[i].desc.mode) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
_mii_cpu_asm_parse_operand(
|
||
|
mii_cpu_asm_program_t *p,
|
||
|
mii_cpu_asm_line_t *l)
|
||
|
{
|
||
|
l->mode = IMPLIED;
|
||
|
if (!l->operand[0])
|
||
|
return 0;
|
||
|
|
||
|
char sep = 0; // (parenthesis)
|
||
|
char *src = l->operand;
|
||
|
switch (*src) {
|
||
|
case '#':
|
||
|
l->mode = IMM;
|
||
|
src = _mii_extract_value_or_name(l, src + 1);
|
||
|
break;
|
||
|
case '$':
|
||
|
l->mode = ABS;
|
||
|
src = _mii_extract_value_or_name(l, src);
|
||
|
break;
|
||
|
case '(':
|
||
|
l->mode = IND;
|
||
|
sep = ')';
|
||
|
src = _mii_extract_value_or_name(l, src + 1);
|
||
|
break;
|
||
|
default: {
|
||
|
l->mode = ABS;
|
||
|
src = _mii_extract_value_or_name(l, src);
|
||
|
_mii_resolve_symbol(p, l, 1);
|
||
|
} break;
|
||
|
}
|
||
|
while (*src == ' ' || *src == '\t') src++;
|
||
|
switch (*src) {
|
||
|
case ',':
|
||
|
src++;
|
||
|
while (*src == ' ' || *src == '\t') src++;
|
||
|
switch (tolower(*src)) {
|
||
|
case 'x':
|
||
|
l->mode = ABS_X;
|
||
|
if (sep) {
|
||
|
// special case for JMP ($xxxx,x)
|
||
|
if (!strcasecmp(l->mnemonic, "JMP"))
|
||
|
l->mode = IND_AX;
|
||
|
else
|
||
|
l->mode = IND_X;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case 'y':
|
||
|
l->mode = ABS_Y;
|
||
|
break;
|
||
|
default:
|
||
|
l->mode = ABS;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case ')':
|
||
|
src++;
|
||
|
switch (*src) {
|
||
|
case ',':
|
||
|
src++;
|
||
|
while (*src == ' ' || *src == '\t') src++;
|
||
|
switch (tolower(*src)) {
|
||
|
case 'x':
|
||
|
l->mode = IND_X;
|
||
|
break;
|
||
|
default:
|
||
|
l->mode = IND_Y;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
l->mode = IND;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
switch (l->mode) {
|
||
|
case ABS: {
|
||
|
if (l->op_value < 0x100) {
|
||
|
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_REL);
|
||
|
if (newo != -1) {
|
||
|
l->mode = ZP_REL;
|
||
|
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||
|
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case ABS_X: {
|
||
|
if (l->op_value < 0x100) {
|
||
|
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_X);
|
||
|
if (newo != -1) {
|
||
|
l->mode = ZP_X;
|
||
|
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||
|
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case ABS_Y: {
|
||
|
if (l->op_value < 0x100) {
|
||
|
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_Y);
|
||
|
if (newo != -1) {
|
||
|
l->mode = ZP_Y;
|
||
|
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||
|
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IND: if (1) {
|
||
|
if (l->op_value < 0x100) {
|
||
|
// printf("Testing if IND opcode %s can be IND_Z operand %s (%04x)\n",
|
||
|
// l->mnemonic, l->operand, l->op_value);
|
||
|
int newo = mii_cpu_opcode_has_mode(l->mnemonic, IND_Z);
|
||
|
if (newo != -1) {
|
||
|
// printf("YES replace %02x with %02x\n",
|
||
|
// l->opcodes[0], newo);
|
||
|
l->mode = IND_Z;
|
||
|
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||
|
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
mii_cpu_asm_assemble(
|
||
|
mii_cpu_asm_program_t *p )
|
||
|
{
|
||
|
mii_cpu_asm_line_t *l = p->prog;
|
||
|
int error = 0;
|
||
|
|
||
|
// fix symbols
|
||
|
l = p->sym;
|
||
|
while (l) {
|
||
|
_mii_extract_value_or_name(l, l->operand);
|
||
|
// printf("SYM %s = %04x\n", l->label, l->op_value);
|
||
|
l = l->sym_next;
|
||
|
}
|
||
|
l = p->prog;
|
||
|
while (l) {
|
||
|
if (!l->mnemonic[0] || l->symbol) {
|
||
|
l = l->next;
|
||
|
continue;
|
||
|
}
|
||
|
l->mode = IMPLIED;
|
||
|
_mii_cpu_asm_parse_operand(p, l);
|
||
|
if (!strcasecmp(l->mnemonic, ".org")) {
|
||
|
if (p->verbose)
|
||
|
printf("%s origin set to $%04x\n", __func__, l->op_value);
|
||
|
if (l->mode == ABS) {
|
||
|
if (!p->org)
|
||
|
p->org = l->op_value;
|
||
|
l->addr_set = 1;
|
||
|
l->addr = l->op_value;
|
||
|
}
|
||
|
l = l->next;
|
||
|
continue;
|
||
|
} else if (!strcasecmp(l->mnemonic, ".verbose")) {
|
||
|
p->verbose = 1;
|
||
|
l = l->next;
|
||
|
continue;
|
||
|
} else if (l->mnemonic[0] == '.') {
|
||
|
l = l->next;
|
||
|
continue;
|
||
|
}
|
||
|
int found = -1;
|
||
|
for (int i = 0; i < 256; i++) {
|
||
|
if (!strncasecmp(l->mnemonic, mii_cpu_op[i].name, 4)) {
|
||
|
if (mii_cpu_op[i].desc.branch) {
|
||
|
l->mode = mii_cpu_op[i].desc.mode;
|
||
|
found = i;
|
||
|
break;
|
||
|
} else if (l->mode == mii_cpu_op[i].desc.mode) {
|
||
|
found = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (found == -1) {
|
||
|
printf("ERROR: %d: %s %s %s\n",
|
||
|
l->line_index, l->label, l->mnemonic, l->operand);
|
||
|
printf(" Missing opcode for %s %d\n", l->mnemonic, l->mode);
|
||
|
error = 1;
|
||
|
break;
|
||
|
}
|
||
|
// printf("FOUND %02x for '%s' '%s' '%s' name:'%s'\n",
|
||
|
// found, l->label, l->mnemonic, l->operand, l->op_name);
|
||
|
l->opcodes[0] = mii_cpu_op[found].desc.op;
|
||
|
l->opcode_count = mii_cpu_op[found].desc.pc;
|
||
|
l = l->next;
|
||
|
}
|
||
|
if (error)
|
||
|
return error;
|
||
|
uint16_t pc = p->org;
|
||
|
/*
|
||
|
* We (should) know all instruction size by now, so lets sets
|
||
|
* the addresses for all the lines
|
||
|
*/
|
||
|
l = p->prog;
|
||
|
while (l) {
|
||
|
if (l->addr_set)
|
||
|
pc = l->addr;
|
||
|
else
|
||
|
l->addr = pc;
|
||
|
if (l->mnemonic[0] != '.') {
|
||
|
// this sets the ones we know about, and clears the ones we dont
|
||
|
for (int i = 1; i < l->opcode_count; i++)
|
||
|
l->opcodes[i] = l->op_value >> (8 * (i - 1));
|
||
|
}
|
||
|
pc += l->opcode_count;
|
||
|
l = l->next;
|
||
|
}
|
||
|
/*
|
||
|
* Now resolve all the operand labels
|
||
|
*/
|
||
|
l = p->prog;
|
||
|
while (l) {
|
||
|
if (l->op_name[0] && !l->label_resolved) {
|
||
|
mii_cpu_asm_line_t *l2 = p->prog;
|
||
|
while (l2) {
|
||
|
int32_t value = 0;
|
||
|
if (!strcasecmp(l->op_name, l2->label)) {
|
||
|
value = l2->op_value;
|
||
|
if (!l2->symbol)
|
||
|
value = l2->addr;
|
||
|
else
|
||
|
value = l2->op_value;
|
||
|
l->op_value = value;
|
||
|
l->label_resolved = 1;
|
||
|
if (mii_cpu_op[l->opcodes[0]].desc.branch)
|
||
|
l->op_value = l2->addr - l->addr - 2;
|
||
|
else if (l->op_low)
|
||
|
l->op_value &= 0xff;
|
||
|
else if (l->op_high)
|
||
|
l->op_value >>= 8;
|
||
|
for (int i = 1; i < l->opcode_count; i++)
|
||
|
l->opcodes[i] = l->op_value >> (8 * (i - 1));
|
||
|
break;
|
||
|
}
|
||
|
l2 = l2->next;
|
||
|
}
|
||
|
if (!l->label_resolved) {
|
||
|
printf("ERROR: Missing label %d: %s %s %s\n",
|
||
|
l->line_index, l->label, l->mnemonic, l->operand);
|
||
|
error = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
l = l->next;
|
||
|
}
|
||
|
if (error)
|
||
|
return error;
|
||
|
p->output_len = p->prog_tail->addr + p->prog_tail->opcode_count - p->org;
|
||
|
if (p->verbose)
|
||
|
printf("%s program at $%04x size %d bytes\n", __func__,
|
||
|
p->org, p->output_len);
|
||
|
p->output = calloc(1, p->output_len);
|
||
|
l = p->prog;
|
||
|
while (l) {
|
||
|
for (int i = 0; i < l->opcode_count; i++)
|
||
|
p->output[l->addr - p->org + i] = l->opcodes[i];
|
||
|
l = l->next;
|
||
|
}
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
mii_cpu_asm(
|
||
|
mii_cpu_asm_program_t *p,
|
||
|
const char *prog)
|
||
|
{
|
||
|
mii_cpu_asm_load(p, prog);
|
||
|
|
||
|
return mii_cpu_asm_assemble(p);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
mii_cpu_asm_free(
|
||
|
mii_cpu_asm_program_t *p)
|
||
|
{
|
||
|
mii_cpu_asm_line_t *l = p->prog;
|
||
|
while (l) {
|
||
|
mii_cpu_asm_line_t *next = l->next;
|
||
|
free(l);
|
||
|
l = next;
|
||
|
}
|
||
|
if (p->output)
|
||
|
free(p->output);
|
||
|
memset(p, 0, sizeof(*p));
|
||
|
}
|