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:
2016-12-18 23:43:41 -08:00
commit 6e2f4096a2
38 changed files with 4424 additions and 0 deletions

39
src/gbasm/assemble.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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