gb-emu: initial commit
Add a mostly non-functional Gameboy CPU and the skeleton of a Gameboy assembler intended for unit tests.
This commit is contained in:
39
src/gbasm/assemble.c
Normal file
39
src/gbasm/assemble.c
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gbasm/assemble.h"
|
||||
#include "gbasm/parser.h"
|
||||
#include "gbasm/types.h"
|
||||
#include "gbasm/errors.h"
|
||||
#include "gbasm/opcodes.h"
|
||||
#include "common.h"
|
||||
|
||||
#define GBASM_MAX_INSTS 1024
|
||||
|
||||
struct gb_asm_prog *prog;
|
||||
|
||||
int gbasm_assemble(char *program, struct emitter *emitter)
|
||||
{
|
||||
struct gbasm_parsed_inst insts[GBASM_MAX_INSTS];
|
||||
int num_insts;
|
||||
|
||||
num_insts = gbasm_parse_buffer(program, insts, ARRAY_SIZE(insts));
|
||||
if (num_insts < 0) {
|
||||
return num_insts;
|
||||
}
|
||||
|
||||
DEBUG_LOG("parsed %d instructions\n", num_insts);
|
||||
|
||||
for (int i = 0; i < num_insts; i++) {
|
||||
DEBUG_LOG("emitting %s\n", insts[i].opcode);
|
||||
const struct gbasm_op_info *info = gbasm_get_opcode_info(insts[i].opcode);
|
||||
if (info == NULL) {
|
||||
gbasm_unknown_opcode_error(&insts[i]);
|
||||
}
|
||||
|
||||
info->check(&insts[i], info);
|
||||
info->emit(emitter, &insts[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
17
src/gbasm/assemble.h
Normal file
17
src/gbasm/assemble.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef GBASM_ASSEMBLE_H
|
||||
#define GBASM_ASSEMBLE_H
|
||||
|
||||
#include "gbasm/emitter.h"
|
||||
|
||||
/**
|
||||
* Assembles an arbitrary gameboy assembly program. The assembled bytes are
|
||||
* outputted using 'emitter.' The string in 'program' will be modified and
|
||||
* should not be referenced after calling gb_asm_assemble.
|
||||
*
|
||||
* @param program: A string containing the program to assemble
|
||||
* @param emitter: An emitter which will output the program on success.
|
||||
* @returns zero on success, else a negative error code
|
||||
*/
|
||||
int gbasm_assemble(char *program, struct emitter *emitter);
|
||||
|
||||
#endif
|
||||
38
src/gbasm/block.h
Normal file
38
src/gbasm/block.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "gbasm/emitter.h"
|
||||
|
||||
#define GB_ASM_INST_MAX_LEN 3 /* I think this is right */
|
||||
#define GB_ASM_PROG_MAX_LEN 4096
|
||||
|
||||
struct gb_asm_inst {
|
||||
struct gb_asm_inst *next;
|
||||
|
||||
/************
|
||||
* Pass one *
|
||||
************/
|
||||
const char *str; /* The source string for the instruction */
|
||||
size_t len; /* The length of assembled instruction in bytes */
|
||||
|
||||
/************
|
||||
* Pass two *
|
||||
************/
|
||||
uint8_t raw[GB_ASM_INST_MAX_LEN]; /* The raw bytes of the assembled instruction */
|
||||
struct gb_asm_block *target;
|
||||
};
|
||||
|
||||
struct gb_asm_block {
|
||||
struct gb_asm_block *next;
|
||||
|
||||
const char *label;
|
||||
uint16_t addr;
|
||||
struct gb_asm_inst *insts;
|
||||
};
|
||||
|
||||
struct gb_asm_prog {
|
||||
char *program;
|
||||
char *instructions;
|
||||
|
||||
struct gb_asm_block *blocks;
|
||||
size_t num_blocks;
|
||||
|
||||
struct emitter *emitter;
|
||||
};
|
||||
64
src/gbasm/emit.c
Normal file
64
src/gbasm/emit.c
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gbasm/emitter.h"
|
||||
#include "common.h"
|
||||
|
||||
int emit(struct emitter *emitter, const void *data, size_t size)
|
||||
{
|
||||
return emitter->emit(emitter->emitter_impl, data, size);
|
||||
}
|
||||
|
||||
int fd_emit(void *emitter, const void *data, size_t size)
|
||||
{
|
||||
struct fd_emitter *fd_emitter = emitter;
|
||||
int rc;
|
||||
|
||||
while ((rc = write(fd_emitter->fd, data, size)) < 0
|
||||
&& (errno != EINTR || errno != EAGAIN)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int buffer_emit(void *emitter, const void *data, size_t size)
|
||||
{
|
||||
struct buffer_emitter *buf_emit = emitter;
|
||||
size_t cpy_len;
|
||||
|
||||
cpy_len = MIN(size, buf_emit->buffer_size - buf_emit->cursor);
|
||||
memcpy(buf_emit->buffer + buf_emit->cursor, data, cpy_len);
|
||||
buf_emit->cursor += cpy_len;
|
||||
|
||||
return cpy_len;
|
||||
}
|
||||
|
||||
int fd_emitter_init(struct emitter *emitter,
|
||||
struct fd_emitter *fd_emitter,
|
||||
int fd)
|
||||
{
|
||||
emitter->emit = fd_emit;
|
||||
emitter->emitter_impl = fd_emitter;
|
||||
|
||||
fd_emitter->fd = fd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int buffer_emitter_init(struct emitter *emitter,
|
||||
struct buffer_emitter *buffer_emitter,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_len)
|
||||
{
|
||||
emitter->emit = buffer_emit;
|
||||
emitter->emitter_impl = buffer_emitter;
|
||||
|
||||
buffer_emitter->buffer = buffer;
|
||||
buffer_emitter->buffer_size = buffer_len;
|
||||
buffer_emitter->cursor = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
44
src/gbasm/emitter.h
Normal file
44
src/gbasm/emitter.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef GBASM_EMITTER_H
|
||||
#define GBASM_EMITTER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct emitter
|
||||
{
|
||||
/* Returns the number of bytes copied on success, else returns a negative error code */
|
||||
int (*emit)(void *emitter, const void *data, size_t size);
|
||||
void (*free)(void *emitter);
|
||||
void *emitter_impl;
|
||||
};
|
||||
|
||||
struct buffer_emitter
|
||||
{
|
||||
uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
size_t cursor;
|
||||
};
|
||||
|
||||
struct fd_emitter
|
||||
{
|
||||
int fd;
|
||||
};
|
||||
|
||||
int fd_emitter_init(struct emitter *emitter,
|
||||
struct fd_emitter *fd_emitter,
|
||||
int fd);
|
||||
|
||||
int buffer_emitter_init(struct emitter *emitter,
|
||||
struct buffer_emitter *buffer_emitter,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_len);
|
||||
|
||||
int emit(struct emitter *emitter,
|
||||
const void *data,
|
||||
size_t size);
|
||||
|
||||
int emit_str(struct emitter *emitter,
|
||||
const char *str);
|
||||
|
||||
|
||||
#endif
|
||||
56
src/gbasm/errors.c
Normal file
56
src/gbasm/errors.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "gbasm/errors.h"
|
||||
#include "gbasm/gb_types.h"
|
||||
#include "gbasm/opcodes.h"
|
||||
#include "gbasm/parser.h"
|
||||
|
||||
static void gbasm_print_error(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
fprintf(stderr, fmt, args);
|
||||
}
|
||||
|
||||
void gbasm_unknown_opcode_error(const struct gbasm_parsed_inst *inst)
|
||||
{
|
||||
gbasm_print_error(
|
||||
"ERROR: %s:%d unrecognized opcode \"%s\"\n",
|
||||
inst->file_name, inst->line_num, inst->opcode);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
void gbasm_too_many_args_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode)
|
||||
{
|
||||
gbasm_print_error(
|
||||
"ERROR: %s:%d too many arguments to \"%s\"\n",
|
||||
inst->file_name, inst->line_num);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void gbasm_arg_wrong_type_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode)
|
||||
{
|
||||
gbasm_print_error(
|
||||
"ERROR: %s:%d invalid operand to \"%s\"\n",
|
||||
inst->file_name, inst->line_num, inst->opcode);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void gbasm_too_few_args_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode)
|
||||
{
|
||||
gbasm_print_error(
|
||||
"ERROR: %s:%d not_enough_args to \"%s\"\n",
|
||||
inst->file_name, inst->line_num, inst->opcode);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
17
src/gbasm/errors.h
Normal file
17
src/gbasm/errors.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef GB_ASM_ERRORS_H
|
||||
#define GB_ASM_ERRORS_H
|
||||
|
||||
#include "gbasm/types.h"
|
||||
|
||||
void gbasm_unknown_opcode_error(const struct gbasm_parsed_inst *inst);
|
||||
|
||||
void gbasm_too_many_args_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode);
|
||||
|
||||
void gbasm_arg_wrong_type_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode);
|
||||
|
||||
void gbasm_too_few_args_error(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *opcode);
|
||||
|
||||
#endif
|
||||
78
src/gbasm/gb_types.h
Normal file
78
src/gbasm/gb_types.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef GBASM_GB_TYPES_H
|
||||
#define GBASM_GB_TYPES_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define GBASM_MAX_OPERANDS 64
|
||||
#define GBASM_MAX_INST_LEN 3
|
||||
|
||||
enum gbasm_operand_type {
|
||||
GBASM_OPERAND_REG_8 = 0x01, /* A, B, etc */
|
||||
GBASM_OPERAND_REG_16 = 0x02, /* BC, DE, etc */
|
||||
GBASM_OPERAND_IMM_8 = 0x04, /* $08, $FF */
|
||||
GBASM_OPERAND_IMM_16 = 0x08, /* $FF40, $0100 */
|
||||
GBASM_OPERAND_ADDR = 0x10, /* START, */
|
||||
GBASM_OPERAND_DEREF = 0x20, /* (HL), (HL+) */
|
||||
GBASM_OPERAND_COND = 0x40, /* C, NZ, etc */
|
||||
};
|
||||
|
||||
enum gbasm_operand_r8_type {
|
||||
GBASM_OPERAND_R8_A,
|
||||
GBASM_OPERAND_R8_B,
|
||||
GBASM_OPERAND_R8_C,
|
||||
GBASM_OPERAND_R8_D,
|
||||
GBASM_OPERAND_R8_E,
|
||||
GBASM_OPERAND_R8_H,
|
||||
GBASM_OPERAND_R8_L,
|
||||
};
|
||||
|
||||
struct gbasm_operand_r8 {
|
||||
enum gbasm_operand_r8_type type;
|
||||
};
|
||||
|
||||
enum gbasm_operand_r16_type {
|
||||
GBASM_OPERAND_R16_BC,
|
||||
GBASM_OPERAND_R16_DE,
|
||||
GBASM_OPERAND_R16_HL,
|
||||
GBASM_OPERAND_R16_SP,
|
||||
GBASM_OPERAND_R16_HL_DEREF,
|
||||
};
|
||||
|
||||
struct gbasm_operand_r16 {
|
||||
enum gbasm_operand_r16_type type;
|
||||
};
|
||||
|
||||
struct gbasm_operand_d8 {
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
struct gbasm_operand_d16 {
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
enum gbasm_operand_cond_type {
|
||||
GBASM_OPERAND_COND_C,
|
||||
GBASM_OPERAND_COND_NC,
|
||||
GBASM_OPERAND_COND_Z,
|
||||
GBASM_OPERAND_COND_NZ,
|
||||
};
|
||||
|
||||
struct gbasm_operand_cond {
|
||||
enum gbasm_operand_cond_type type;
|
||||
};
|
||||
|
||||
struct gbasm_operand {
|
||||
|
||||
enum gbasm_operand_type type;
|
||||
|
||||
union {
|
||||
struct gbasm_operand_r8 r8;
|
||||
struct gbasm_operand_r16 r16;
|
||||
struct gbasm_operand_d8 d8;
|
||||
struct gbasm_operand_d16 d16;
|
||||
struct gbasm_operand_cond cond;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
284
src/gbasm/opcodes.c
Normal file
284
src/gbasm/opcodes.c
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "gbasm/gb_types.h"
|
||||
#include "gbasm/opcodes.h"
|
||||
#include "gbasm/errors.h"
|
||||
|
||||
#include "tri.h"
|
||||
#include "common.h"
|
||||
|
||||
#define MAX_OPCODE_LEN 10 /*TODO: Check that's enough */
|
||||
|
||||
static bool opcodes_initted = false;
|
||||
static struct tri opcode_tri;
|
||||
|
||||
bool gbasm_argtype_in_set(uint32_t argtype_set, uint32_t argtype)
|
||||
{
|
||||
return !!(argtype & argtype_set);
|
||||
}
|
||||
|
||||
static int check_no_args(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *op_info)
|
||||
{
|
||||
if (inst->num_operands > 0) {
|
||||
gbasm_too_many_args_error(inst, op_info);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t length_one_byte(
|
||||
const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *info)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define GEN_FIXED_EMITFN(_name, _data_array) \
|
||||
size_t _name(struct emitter *emitter, \
|
||||
const struct gbasm_parsed_inst *inst) \
|
||||
{ \
|
||||
const uint8_t raw[] = _data_array; \
|
||||
emit(emitter, raw, sizeof(raw)); \
|
||||
return sizeof(raw); \
|
||||
}
|
||||
|
||||
GEN_FIXED_EMITFN(nop_emit, {0x00})
|
||||
GEN_FIXED_EMITFN(halt_emit, {0x76})
|
||||
GEN_FIXED_EMITFN(stop_emit, {0x10})
|
||||
GEN_FIXED_EMITFN(di_emit, {0xf3})
|
||||
GEN_FIXED_EMITFN(ei_emit, {0xfb})
|
||||
GEN_FIXED_EMITFN(rla_emit, {0x17})
|
||||
GEN_FIXED_EMITFN(rra_emit, {0x1f})
|
||||
GEN_FIXED_EMITFN(rlca_emit, {0x07})
|
||||
GEN_FIXED_EMITFN(rrca_emit, {0x0f})
|
||||
GEN_FIXED_EMITFN(daa_emit, {0x27})
|
||||
GEN_FIXED_EMITFN(scf_emit, {0x37})
|
||||
GEN_FIXED_EMITFN(reti_emit, {0xd9})
|
||||
GEN_FIXED_EMITFN(cpl_emit, {0x2f})
|
||||
GEN_FIXED_EMITFN(ccf_emit, {0x3f})
|
||||
|
||||
int inc_dec_check(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *op_info)
|
||||
{
|
||||
if (inst->num_operands > 1) {
|
||||
gbasm_too_many_args_error(inst, op_info);
|
||||
}
|
||||
|
||||
if (inst->num_operands < 1) {
|
||||
gbasm_too_few_args_error(inst, op_info);
|
||||
}
|
||||
|
||||
if (!gbasm_argtype_in_set(op_info->operand_types[0], inst->operands[0].type)) {
|
||||
gbasm_arg_wrong_type_error(inst, op_info);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t inc_emit(struct emitter *emitter,
|
||||
const struct gbasm_parsed_inst *inst)
|
||||
{
|
||||
uint8_t opcode = 0x00;
|
||||
|
||||
switch (inst->operands[0].type) {
|
||||
case GBASM_OPERAND_REG_8:
|
||||
switch (inst->operands[0].r8.type) {
|
||||
case GBASM_OPERAND_R8_A: opcode = 0x3c; break;
|
||||
case GBASM_OPERAND_R8_B: opcode = 0x04; break;
|
||||
case GBASM_OPERAND_R8_C: opcode = 0x0c; break;
|
||||
case GBASM_OPERAND_R8_D: opcode = 0x14; break;
|
||||
case GBASM_OPERAND_R8_E: opcode = 0x1c; break;
|
||||
case GBASM_OPERAND_R8_H: opcode = 0x24; break;
|
||||
case GBASM_OPERAND_R8_L: opcode = 0x2c; break;
|
||||
}
|
||||
break;
|
||||
case GBASM_OPERAND_REG_16:
|
||||
switch (inst->operands[0].r16.type) {
|
||||
case GBASM_OPERAND_R16_BC: opcode = 0x03; break;
|
||||
case GBASM_OPERAND_R16_DE: opcode = 0x13; break;
|
||||
case GBASM_OPERAND_R16_HL: opcode = 0x23; break;
|
||||
case GBASM_OPERAND_R16_SP: opcode = 0x33; break;
|
||||
case GBASM_OPERAND_R16_HL_DEREF: opcode = 0x34; break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT(0);
|
||||
}
|
||||
|
||||
emit(emitter, &opcode, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t dec_emit(struct emitter *emitter,
|
||||
const struct gbasm_parsed_inst *inst)
|
||||
{
|
||||
uint8_t opcode = 0x00;
|
||||
|
||||
switch (inst->operands[0].type) {
|
||||
case GBASM_OPERAND_REG_8:
|
||||
switch (inst->operands[0].r8.type) {
|
||||
case GBASM_OPERAND_R8_A: opcode = 0x3d; break;
|
||||
case GBASM_OPERAND_R8_B: opcode = 0x05; break;
|
||||
case GBASM_OPERAND_R8_C: opcode = 0x0d; break;
|
||||
case GBASM_OPERAND_R8_D: opcode = 0x15; break;
|
||||
case GBASM_OPERAND_R8_E: opcode = 0x1d; break;
|
||||
case GBASM_OPERAND_R8_H: opcode = 0x25; break;
|
||||
case GBASM_OPERAND_R8_L: opcode = 0x2d; break;
|
||||
}
|
||||
break;
|
||||
case GBASM_OPERAND_REG_16:
|
||||
switch (inst->operands[0].r16.type) {
|
||||
case GBASM_OPERAND_R16_BC: opcode = 0x0b; break;
|
||||
case GBASM_OPERAND_R16_DE: opcode = 0x1b; break;
|
||||
case GBASM_OPERAND_R16_HL: opcode = 0x2b; break;
|
||||
case GBASM_OPERAND_R16_SP: opcode = 0x3b; break;
|
||||
case GBASM_OPERAND_R16_HL_DEREF: opcode = 0x35; break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT(0);
|
||||
}
|
||||
|
||||
emit(emitter, &opcode, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
struct gbasm_op_info gbasm_op_infos[] = {
|
||||
{
|
||||
.opcode = "nop",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = nop_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "halt",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = halt_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "stop",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = stop_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "di",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = di_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "ei",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = ei_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "rla",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = rla_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "rra",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = rra_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "rlca",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = rlca_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "rrca",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = rrca_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "daa",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = daa_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "scf",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = scf_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "reti",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = reti_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "cpl",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = cpl_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "ccf",
|
||||
.operand_types = {},
|
||||
.check = check_no_args,
|
||||
.length = length_one_byte,
|
||||
.emit = ccf_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "inc",
|
||||
/* TODO: support inc (HL) */
|
||||
.operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 },
|
||||
.check = inc_dec_check,
|
||||
.length = length_one_byte,
|
||||
.emit = inc_emit,
|
||||
},
|
||||
{
|
||||
.opcode = "dec",
|
||||
/* TODO: support inc (HL) */
|
||||
.operand_types = { GBASM_OPERAND_REG_8 | GBASM_OPERAND_REG_16 },
|
||||
.check = inc_dec_check,
|
||||
.length = length_one_byte,
|
||||
.emit = dec_emit,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
static void init_opcode_tri()
|
||||
{
|
||||
int i;
|
||||
tri_init(&opcode_tri);
|
||||
opcodes_initted = true;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(gbasm_op_infos); i++) {
|
||||
tri_add_string(&opcode_tri, gbasm_op_infos[i].opcode, &gbasm_op_infos[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const struct gbasm_op_info *gbasm_get_opcode_info(const char *opcode)
|
||||
{
|
||||
if (!opcodes_initted) {
|
||||
init_opcode_tri();
|
||||
}
|
||||
|
||||
return tri_get_string(&opcode_tri, opcode);
|
||||
}
|
||||
12
src/gbasm/opcodes.h
Normal file
12
src/gbasm/opcodes.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef GBASM_OPCODES_H
|
||||
#define GBASM_OPCODES_H
|
||||
|
||||
#include "gbasm/types.h"
|
||||
|
||||
/**
|
||||
* Given a downcased opcode token, gets teh gbasm_op_info structure
|
||||
* which describes it.
|
||||
*/
|
||||
const struct gbasm_op_info *gbasm_get_opcode_info(const char *opcode);
|
||||
|
||||
#endif
|
||||
212
src/gbasm/operands.c
Normal file
212
src/gbasm/operands.c
Normal file
@@ -0,0 +1,212 @@
|
||||
#include "gbasm/gb_types.h"
|
||||
#include "gbasm/opcodes.h"
|
||||
#include "gbasm/errors.h"
|
||||
|
||||
#include "tri.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static bool operands_initted = false;
|
||||
static struct tri operand_tri;
|
||||
static struct tri prefixes_tri;
|
||||
|
||||
static const struct gbasm_operand_info gbasm_operand_infos[] = {
|
||||
{
|
||||
.token = "a",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_A },
|
||||
}
|
||||
},
|
||||
{
|
||||
.token = "b",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_B },
|
||||
},
|
||||
},
|
||||
{
|
||||
/* TODO: handle C condition code */
|
||||
.token = "c",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_C },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "d",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_D },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "e",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_E },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "h",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_H },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "l",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_8,
|
||||
.r8 = { .type = GBASM_OPERAND_R8_L },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "bc",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_16,
|
||||
.r16 = { GBASM_OPERAND_R16_BC },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "de",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_16,
|
||||
.r16 = { GBASM_OPERAND_R16_DE },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "hl",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_16,
|
||||
.r16 = { GBASM_OPERAND_R16_HL },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "(hl)",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_16,
|
||||
.r16 = { GBASM_OPERAND_R16_HL_DEREF },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
.token = "sp",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_REG_16,
|
||||
.r16 = { GBASM_OPERAND_R16_SP },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "nc",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_COND,
|
||||
.cond = { GBASM_OPERAND_COND_NC },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "z",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_COND,
|
||||
.cond = { GBASM_OPERAND_COND_Z },
|
||||
},
|
||||
},
|
||||
{
|
||||
.token = "nz",
|
||||
.fixed = true,
|
||||
.op_info = {
|
||||
.type = GBASM_OPERAND_COND,
|
||||
.cond = { GBASM_OPERAND_COND_NZ },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static int convert_constant(const char *token, struct gbasm_operand *info)
|
||||
{
|
||||
int val = strtol(token + 1, NULL, 0);
|
||||
|
||||
if (val <= 0xff || val >= -0xff) {
|
||||
info->type = GBASM_OPERAND_IMM_8;
|
||||
info->d8.value = val;
|
||||
} else if (val <= 0xffff || val >= 0xffff) {
|
||||
info->type = GBASM_OPERAND_IMM_16;
|
||||
info->d16.value = val;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct gbasm_operand_prefix prefixes[] = {
|
||||
{
|
||||
.prefix = "$",
|
||||
.convert = convert_constant,
|
||||
},
|
||||
};
|
||||
|
||||
static void init_opcode_tri()
|
||||
{
|
||||
int i;
|
||||
|
||||
tri_init(&operand_tri);
|
||||
tri_init(&prefixes_tri);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(gbasm_operand_infos); i++) {
|
||||
tri_add_string(&operand_tri,
|
||||
gbasm_operand_infos[i].token,
|
||||
(void *) &gbasm_operand_infos[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(prefixes); i++) {
|
||||
tri_add_string(&prefixes_tri,
|
||||
prefixes[i].prefix,
|
||||
(void *) &prefixes[i]);
|
||||
}
|
||||
|
||||
operands_initted = true;
|
||||
|
||||
}
|
||||
|
||||
int gbasm_parse_operand(const char *token, struct gbasm_operand *out)
|
||||
{
|
||||
struct gbasm_operand_info *info;
|
||||
struct gbasm_operand_prefix *prefix;
|
||||
|
||||
if (!operands_initted) {
|
||||
init_opcode_tri();
|
||||
}
|
||||
|
||||
info = (struct gbasm_operand_info *) tri_get_string(&operand_tri, token);
|
||||
if (info != NULL) {
|
||||
if (info->fixed) {
|
||||
memcpy(out, &info->op_info, sizeof(*out));
|
||||
} else {
|
||||
info->convert(token, out);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
prefix = (struct gbasm_operand_prefix *) tri_prefix_match(&prefixes_tri, token);
|
||||
if (prefix != NULL) {
|
||||
prefix->convert(token, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
12
src/gbasm/operands.h
Normal file
12
src/gbasm/operands.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef GBASM_OPCODES_H
|
||||
#define GBASM_OPCODES_H
|
||||
|
||||
#include "gbasm/types.h"
|
||||
|
||||
/**
|
||||
* Given a downcased opcode token, gets teh gbasm_op_info structure
|
||||
* which describes it.
|
||||
*/
|
||||
int gbasm_parse_operand(const char *token, struct gbasm_operand *out);
|
||||
|
||||
#endif
|
||||
123
src/gbasm/parser.c
Normal file
123
src/gbasm/parser.c
Normal file
@@ -0,0 +1,123 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "gbasm/parser.h"
|
||||
#include "gbasm/gb_types.h"
|
||||
#include "gbasm/operands.h"
|
||||
|
||||
#define GBASM_MAX_TOKENS (GBASM_MAX_OPERANDS + 1) /* One for opcode */
|
||||
#define GBASM_LINE_DELIMS "\n"
|
||||
#define GBASM_COMMENT_DELIMS ";"
|
||||
#define GBASM_WHITESPACE_DELIMS " \t"
|
||||
|
||||
/* Returns the number of tokens in the line */
|
||||
static int gbasm_tokenize_line(char *line,
|
||||
char **tokens,
|
||||
int max_tokens,
|
||||
char **next_line)
|
||||
{
|
||||
int num_tokens = 0;
|
||||
char *newline;
|
||||
|
||||
/* Find the next line */
|
||||
newline = strchr(line, '\n');
|
||||
if (newline != NULL) {
|
||||
*newline = '\0';
|
||||
*next_line = newline + 1;
|
||||
} else {
|
||||
*next_line = NULL;
|
||||
};
|
||||
|
||||
/* Strip any comments on this line */
|
||||
strtok(line, GBASM_COMMENT_DELIMS);
|
||||
|
||||
for (tokens[num_tokens] = strtok(line, GBASM_WHITESPACE_DELIMS);
|
||||
tokens[num_tokens] != NULL && num_tokens < max_tokens;
|
||||
tokens[num_tokens] = strtok(NULL, GBASM_WHITESPACE_DELIMS))
|
||||
{
|
||||
num_tokens++;
|
||||
}
|
||||
|
||||
return num_tokens;
|
||||
}
|
||||
|
||||
int gbasm_parse_tokens(struct gbasm_parsed_inst *inst,
|
||||
const char **tokens,
|
||||
int num_tokens)
|
||||
{
|
||||
int token_num = 0;
|
||||
int operand_num = 0;
|
||||
|
||||
if (num_tokens <= 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (tokens[token_num][0] == '.') {
|
||||
token_num++;
|
||||
/* TODO: support labels */
|
||||
}
|
||||
|
||||
if (token_num >= num_tokens) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
inst->opcode = tokens[token_num++];
|
||||
while (token_num < num_tokens) {
|
||||
/* TODO error check */
|
||||
gbasm_parse_operand(tokens[token_num++],
|
||||
&inst->operands[operand_num++]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gbasm_parse_buffer(char *buffer,
|
||||
struct gbasm_parsed_inst *insts,
|
||||
unsigned int max_insts)
|
||||
{
|
||||
char *line_tokens[GBASM_MAX_TOKENS] = { 0 };
|
||||
char *next_line, *cur;
|
||||
int num_tokens;
|
||||
int line_num;
|
||||
int inst_num = 0;
|
||||
|
||||
if (buffer != NULL) {
|
||||
downcase(buffer);
|
||||
}
|
||||
|
||||
for (cur = buffer, line_num = 0;
|
||||
cur != NULL;
|
||||
cur = next_line, line_num++) {
|
||||
|
||||
num_tokens = gbasm_tokenize_line(cur,
|
||||
line_tokens,
|
||||
GBASM_MAX_TOKENS,
|
||||
&next_line);
|
||||
|
||||
ASSERT_MSG(num_tokens >= 0, "failed to parse line %d", line_num);
|
||||
|
||||
if (num_tokens > 0) {
|
||||
gbasm_parse_tokens(&insts[inst_num],
|
||||
(const char **)line_tokens,
|
||||
num_tokens);
|
||||
DEBUG_LOG("token: %s\n", insts[inst_num].opcode);
|
||||
insts[inst_num].line_num = line_num;
|
||||
|
||||
/* TODO: Fill these out */
|
||||
insts[inst_num].file_name = "";
|
||||
insts[inst_num].src_line = "";
|
||||
insts[inst_num].num_operands = num_tokens - 1;
|
||||
|
||||
if (++inst_num >= max_insts) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return inst_num;
|
||||
}
|
||||
19
src/gbasm/parser.h
Normal file
19
src/gbasm/parser.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef GBASM_PARSER_H
|
||||
#define GBASM_PARSER_H
|
||||
|
||||
#include "gbasm/types.h"
|
||||
|
||||
/**
|
||||
* Parses an input assembly program into an array of parsed_instructions
|
||||
*
|
||||
* @param buffer: the input program, which will be modified
|
||||
* @param insts: the output array of instructions
|
||||
* @param max_insts: the maximum number of instructions which can be put
|
||||
* into 'insts'
|
||||
* @returns zero on success, else a negative error code
|
||||
*/
|
||||
int gbasm_parse_buffer(char *buffer,
|
||||
struct gbasm_parsed_inst *insts,
|
||||
unsigned int max_insts);
|
||||
|
||||
#endif
|
||||
49
src/gbasm/types.h
Normal file
49
src/gbasm/types.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef GBASM_TYPES_H
|
||||
#define GBASM_TYPES_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gbasm/gb_types.h"
|
||||
#include "gbasm/emitter.h"
|
||||
|
||||
struct gbasm_parsed_inst {
|
||||
const char *src_line;
|
||||
const char *file_name;
|
||||
const char *opcode;
|
||||
int line_num;
|
||||
|
||||
struct gbasm_operand operands[GBASM_MAX_OPERANDS];
|
||||
int num_operands;
|
||||
};
|
||||
|
||||
struct gbasm_op_info {
|
||||
char *opcode;
|
||||
uint32_t operand_types[GBASM_MAX_OPERANDS];
|
||||
|
||||
int (*check)(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *op_info);
|
||||
size_t (*length)(const struct gbasm_parsed_inst *inst,
|
||||
const struct gbasm_op_info *info);
|
||||
size_t (*emit)(struct emitter *emitter,
|
||||
const struct gbasm_parsed_inst *inst);
|
||||
};
|
||||
|
||||
struct gbasm_operand_info {
|
||||
|
||||
const char *token;
|
||||
|
||||
bool fixed;
|
||||
union {
|
||||
/* Valid iff fixed == true */
|
||||
int (*convert)(const char *token, struct gbasm_operand *operand);
|
||||
/* Valid iff fixed == false */
|
||||
struct gbasm_operand op_info;
|
||||
};
|
||||
};
|
||||
|
||||
struct gbasm_operand_prefix {
|
||||
const char *prefix;
|
||||
int (*convert)(const char *token, struct gbasm_operand *operand);
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user