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:
79
src/apps/gbasm-test.c
Normal file
79
src/apps/gbasm-test.c
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "gbasm/assemble.h"
|
||||
#include "gbasm/emitter.h"
|
||||
#include "tests/gbasm/test.h"
|
||||
#include "tests/gbasm/inc.h"
|
||||
#include "tests/gbasm/fixed.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <string.h> /* memset */
|
||||
#include <glib.h>
|
||||
|
||||
const static struct gbasm_tests *tests[] = {
|
||||
&gbasm_fixed_tests,
|
||||
&gbasm_inc_tests,
|
||||
};
|
||||
|
||||
static int cmp_mem(const uint8_t *test_data, const uint8_t *expected_data, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (test_data[i] != expected_data[i]) {
|
||||
g_test_message("memory contents differed at index %zu, data=%x, expected=%x",
|
||||
i, test_data[i], expected_data[i]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_test(const void *test_data)
|
||||
{
|
||||
static uint8_t program[4096];
|
||||
const struct gbasm_test *test = test_data;
|
||||
struct emitter emitter;
|
||||
struct buffer_emitter buffer_emitter;
|
||||
char *src;
|
||||
int rc;
|
||||
size_t len;
|
||||
|
||||
src = strdup(test->asm_source);
|
||||
|
||||
memset(program, 0, sizeof(program));
|
||||
buffer_emitter_init(&emitter, &buffer_emitter, program, sizeof(program));
|
||||
|
||||
rc = gbasm_assemble(src, &emitter);
|
||||
g_assert_cmpint(rc, ==, 0);
|
||||
|
||||
len = test->expected_output_len;
|
||||
|
||||
if (len != buffer_emitter.cursor) {
|
||||
g_warning("output lengths were not equal expected %zu, actual %zu",
|
||||
len, buffer_emitter.cursor);
|
||||
g_test_fail();
|
||||
len = MIN(len, buffer_emitter.cursor);
|
||||
}
|
||||
|
||||
rc = cmp_mem(program, test->expected_output, len);
|
||||
g_assert_cmpint(rc, ==, 0);
|
||||
|
||||
free(src);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char test_name_buff[256];
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
for (int j = 0; j < tests[i]->num_tests; j++) {
|
||||
snprintf(test_name_buff, sizeof(test_name_buff),
|
||||
"/gbasm/%s/%s",
|
||||
tests[i]->name,
|
||||
tests[i]->tests[j]->name);
|
||||
g_test_add_data_func(test_name_buff, tests[i]->tests[j], run_test);
|
||||
}
|
||||
}
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
87
src/apps/gbasm.c
Normal file
87
src/apps/gbasm.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/* A simple, two-pass gameboy assembler */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "gbasm/emitter.h"
|
||||
#include "gbasm/assemble.h"
|
||||
|
||||
#define GBASM_FILE_BUFFER_SIZE (1024 * 1024)
|
||||
|
||||
static char file_buffer[GBASM_FILE_BUFFER_SIZE];
|
||||
|
||||
static void usage()
|
||||
{
|
||||
printf("gbasm -i <file> -o <file>\n"
|
||||
" -h Print this usage \n"
|
||||
" -i <input file> The file to assemble\n"
|
||||
" -o <output file> The file to write the assembled program to\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct emitter emitter;
|
||||
struct fd_emitter fd_emitter;
|
||||
char opt;
|
||||
int in_fd = -1;
|
||||
int out_fd = -1;
|
||||
|
||||
while ((opt = getopt(argc, argv, "i:o:h")) != -1) {
|
||||
|
||||
switch (opt) {
|
||||
case 'o':
|
||||
if (out_fd >= 0) {
|
||||
close(out_fd);
|
||||
}
|
||||
|
||||
out_fd = open(optarg, O_WRONLY | O_CREAT);
|
||||
if (out_fd < 0) {
|
||||
fprintf(stderr, "failed to open output file %s\n", optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if (in_fd >= 0) {
|
||||
close(in_fd);
|
||||
}
|
||||
|
||||
in_fd = open(optarg, O_RDONLY);
|
||||
if (in_fd < 0) {
|
||||
fprintf(stderr, "failed to open input file %s\n", optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
exit(1);
|
||||
case ':':
|
||||
fprintf(stderr, "Option -%c requires argument\n", optopt);
|
||||
usage();
|
||||
exit(1);
|
||||
case '?':
|
||||
fprintf(stderr, "Unrecognized option -%c\n", optopt);
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_fd < 0) {
|
||||
exit(1);
|
||||
} else {
|
||||
pread(in_fd, file_buffer, GBASM_FILE_BUFFER_SIZE - 1, 0);
|
||||
file_buffer[GBASM_FILE_BUFFER_SIZE - 1] = '\0';
|
||||
}
|
||||
|
||||
if (out_fd < 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fd_emitter_init(&emitter, &fd_emitter, out_fd);
|
||||
|
||||
return gbasm_assemble(file_buffer, &emitter);
|
||||
}
|
||||
525
src/apps/gbdb.c
Normal file
525
src/apps/gbdb.c
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* A CLI-based tester and debugger for the Gameboy's LR35902 CPU
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "gb_disas.h"
|
||||
#include "cpu.h"
|
||||
#include "video.h"
|
||||
#include "tri.h"
|
||||
|
||||
#define INPUT_MAX_LEN 512
|
||||
#define MAX_BREAKPTS 256 /* Should be plenty for anyone */
|
||||
#define MAX_RAM_LEN (1 << 20) /* Up to 8Mb Cartridge */
|
||||
#define UNMAP_BOOTROM_ADDR 0xff50
|
||||
|
||||
typedef void (gbdb_cmd)(char *arg_string);
|
||||
|
||||
static bool cpu_at_breakpoint(void);
|
||||
|
||||
static uint8_t ram[MAX_RAM_LEN];
|
||||
static const char prompt[] = "gbdb >";
|
||||
static const char usage[] =
|
||||
"Available commands:\n"
|
||||
"load <file>: loads the given file as the gameboy cartridge\n"
|
||||
"run: runs the CPU until a breakpoint or halt is hit\n"
|
||||
"break <addr>: Adds a breakpoint for the given addess\n"
|
||||
"step <cycles>: executes \"cycle\" instructions (default 1)\n"
|
||||
"regs: dumps the state of the registers\n"
|
||||
"peek: view the next instruction to run\n"
|
||||
"exit: quit the program\n";
|
||||
|
||||
static const char *reg16_names[] = {
|
||||
"AF",
|
||||
"BC",
|
||||
"DE",
|
||||
"HL",
|
||||
"SP",
|
||||
"PC",
|
||||
};
|
||||
|
||||
static const char *reg8_names[] = {
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"H",
|
||||
"L",
|
||||
"N/A",
|
||||
"A",
|
||||
"F"
|
||||
};
|
||||
|
||||
static struct lr35902_state cpu;
|
||||
static struct gb_video video;
|
||||
|
||||
static unsigned char bootrom[0x100] = {0};
|
||||
|
||||
static int bootrom_mapped = 1;
|
||||
static volatile sig_atomic_t paused = 0;
|
||||
|
||||
|
||||
static void break_execution_handler(int signum)
|
||||
{
|
||||
paused = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ram_{read,write} work easily because the cartridge is mapped to 0x0000
|
||||
*
|
||||
*/
|
||||
|
||||
static void strip_newline(char *string)
|
||||
{
|
||||
char *pos;
|
||||
|
||||
if (string == NULL)
|
||||
return;
|
||||
|
||||
if ((pos=strchr(string, '\n')) != NULL) {
|
||||
*pos = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t mem_read(struct lr35902_state *cpu, uint16_t addr)
|
||||
{
|
||||
|
||||
switch (addr) {
|
||||
case 0 ... 0x100-1:
|
||||
if (bootrom_mapped) {
|
||||
return bootrom[addr];
|
||||
} else {
|
||||
return ram[addr];
|
||||
}
|
||||
case 0xFF40 ... 0xFF4B:
|
||||
return gb_video_mem_read(&video, addr);
|
||||
default:
|
||||
return ram[addr];
|
||||
}
|
||||
}
|
||||
|
||||
static void mem_write(struct lr35902_state *cpu, uint16_t addr, uint8_t val)
|
||||
{
|
||||
|
||||
switch (addr) {
|
||||
case UNMAP_BOOTROM_ADDR:
|
||||
if (val == 1) {
|
||||
bootrom_mapped = 0;
|
||||
printf("bootrom unmapped\n");
|
||||
}
|
||||
break;
|
||||
case 0xFF40 ... 0xFF4B:
|
||||
gb_video_mem_write(&video, addr, val);
|
||||
break;
|
||||
case 0 ... 0x100:
|
||||
if (bootrom_mapped) {
|
||||
break;
|
||||
}
|
||||
/* Intentional fall-through */
|
||||
default:
|
||||
ram[addr] = val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void mem_dump(char *filename)
|
||||
{
|
||||
//TODO: Check error codes like a real programmer :P
|
||||
FILE* dump_file;
|
||||
char *token = strtok(filename, " ");
|
||||
|
||||
if (token == NULL) {
|
||||
printf("usage: load <file>\n");
|
||||
return;
|
||||
}
|
||||
strip_newline(token);
|
||||
dump_file = fopen(token, "w");
|
||||
if(dump_file < 0) {
|
||||
printf("Failed to open mem dump file: %d\n", errno);
|
||||
return;
|
||||
}
|
||||
fwrite(ram, MAX_RAM_LEN, 1, dump_file);
|
||||
fclose(dump_file);
|
||||
}
|
||||
|
||||
static void init(void)
|
||||
{
|
||||
lr35902_init(&cpu, mem_read, mem_write);
|
||||
gb_video_init(&video);
|
||||
memset(&ram, 0, MAX_RAM_LEN);
|
||||
bootrom_mapped = 1;
|
||||
}
|
||||
|
||||
static void cycle()
|
||||
{
|
||||
lr35902_cycle(&cpu);
|
||||
gb_video_cycle(&video);
|
||||
}
|
||||
|
||||
static void show_prompt()
|
||||
{
|
||||
printf("%s ", prompt);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void step(char *arg_list)
|
||||
{
|
||||
uint64_t steps;
|
||||
int i = 0;
|
||||
char *token;
|
||||
token = strtok(arg_list, " ");
|
||||
if (token == NULL) {
|
||||
steps = 1;
|
||||
} else {
|
||||
strip_newline(token);
|
||||
steps = strtol(token, NULL, 0);
|
||||
}
|
||||
|
||||
paused = 0;
|
||||
signal(SIGINT, break_execution_handler);
|
||||
|
||||
do {
|
||||
cycle();
|
||||
i++;
|
||||
} while (i < steps && !cpu_at_breakpoint() && !cpu.halted && !paused);
|
||||
|
||||
if (i == steps) {
|
||||
printf("CPU stopped after %d cycles\n", i);
|
||||
} else if (cpu_at_breakpoint()) {
|
||||
printf("Breakpoint hit\n");
|
||||
} else if (cpu.halted) {
|
||||
printf("CPU halted\n");
|
||||
} else {
|
||||
printf("Interrupted after %d cycles\n", i);
|
||||
}
|
||||
}
|
||||
|
||||
static void regs(char *arg_list)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < NUM_LR35902_REGS_8; i++){
|
||||
if (i != LR35902_REG_HL_DEREF) {
|
||||
printf("%s: 0x%02x\n", reg8_names[i], lr35902_get_reg_8(&cpu, i));
|
||||
};
|
||||
}
|
||||
for(i = 0; i < NUM_LR35902_REGS_16; i++){
|
||||
printf("%s: 0x%04x\n", reg16_names[i], lr35902_get_reg_16(&cpu, i));
|
||||
}
|
||||
}
|
||||
|
||||
static void peek(char *arg_list)
|
||||
{
|
||||
uint16_t pc = lr35902_get_reg_16(&cpu, LR35902_REG_PC);
|
||||
uint8_t byte = mem_read(&cpu, pc);
|
||||
printf("0x%04x:%s\n", pc, gb_byte_to_opcode(byte));
|
||||
}
|
||||
|
||||
static void stats(char *arg_list)
|
||||
{
|
||||
printf("Cycles: %lu\n", cpu.metrics.cycles);
|
||||
printf("Retired Insructions %lu\n", cpu.metrics.retired_instrs);
|
||||
printf("Memory Reads: %lu\n", cpu.metrics.mem_reads);
|
||||
printf("Memory Writes: %lu\n", cpu.metrics.mem_writes);
|
||||
}
|
||||
|
||||
static void help(char *arg_list)
|
||||
{
|
||||
printf(usage);
|
||||
}
|
||||
|
||||
static void mem(char *arg_list)
|
||||
{
|
||||
uint16_t addr, bytes, i;
|
||||
char *token = strtok(arg_list, " ");
|
||||
|
||||
if (token == NULL) {
|
||||
printf("usage: mem <addr> (num bytes) (format)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
addr = strtol(token, NULL, 0);
|
||||
token = strtok(NULL, " ");
|
||||
if (token == NULL) {
|
||||
bytes = 1;
|
||||
} else {
|
||||
bytes = strtol(token, NULL, 0);
|
||||
}
|
||||
|
||||
token = strtok(NULL, " ");
|
||||
|
||||
for (i = 0; i < bytes; i++) {
|
||||
//TODO: Make sure this has no side effects
|
||||
int val = mem_read(&cpu, addr + i);
|
||||
|
||||
if (token != NULL && token[0] == 'i') {
|
||||
printf("0x%04x:%s\n", addr + i, gb_byte_to_opcode(val));
|
||||
} else if (token != NULL && token[0] == 'b') {
|
||||
printf("0x%04x:0x%02x %s\n", addr + i, val, gb_byte_to_opcode(val));
|
||||
} else {
|
||||
printf("0x%04x:0x%02x\n", addr + i, val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void load_file_to_buffer(char *filename, uint8_t *buffer, size_t size)
|
||||
{
|
||||
//TODO: Check error codes like a real programmer :P
|
||||
FILE* prog_file;
|
||||
long filesize;
|
||||
|
||||
prog_file = fopen(filename, "r");
|
||||
if(prog_file < 0) {
|
||||
printf("Failed to load game file: %d\n", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(prog_file, 0, SEEK_END);
|
||||
filesize = ftell(prog_file);
|
||||
fseek(prog_file, 0, SEEK_SET);
|
||||
fread(buffer, MIN(filesize, size), 1, prog_file);
|
||||
fclose(prog_file);
|
||||
|
||||
}
|
||||
|
||||
static void load(char *arg_list)
|
||||
{
|
||||
char *token = strtok(arg_list, " ");
|
||||
if (token == NULL) {
|
||||
printf("usage: load <file>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
strip_newline(token);
|
||||
load_file_to_buffer(token, ram, sizeof(ram));
|
||||
}
|
||||
|
||||
static void load_bootrom(char *arg_list)
|
||||
{
|
||||
char *token = strtok(arg_list, " ");
|
||||
if (token == NULL) {
|
||||
printf("usage: bootrom <file>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
strip_newline(token);
|
||||
load_file_to_buffer(token, bootrom, sizeof(bootrom));
|
||||
}
|
||||
|
||||
|
||||
static void quit(char *arg_list)
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void run(char *arg_list)
|
||||
{
|
||||
paused = 0;
|
||||
signal(SIGINT, break_execution_handler);
|
||||
while(!cpu.halted && !cpu_at_breakpoint() && !paused) {
|
||||
cycle();
|
||||
}
|
||||
|
||||
if (cpu.halted) {
|
||||
printf("CPU halted after %ld cycles\n", cpu.metrics.cycles);
|
||||
} else if (paused) {
|
||||
printf("Interrupted.\n");
|
||||
} else {
|
||||
printf("Breakpoint hit\n");
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
uint16_t addr;
|
||||
bool active;
|
||||
} breakpoints[MAX_BREAKPTS];
|
||||
|
||||
static void set_breakpoint(char *arg_string)
|
||||
{
|
||||
uint16_t addr, i;
|
||||
char *token = strtok(arg_string, " ");
|
||||
|
||||
if (token == NULL) {
|
||||
printf("usage: breakpoint add <addr>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
addr = strtol(token, NULL, 0);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(breakpoints); i++) {
|
||||
if (breakpoints[i].active == false) {
|
||||
breakpoints[i].addr = addr;
|
||||
breakpoints[i].active = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printf("maximum number of breakpoints reached\n");
|
||||
}
|
||||
|
||||
static void delete_breakpoint(char *arg_string)
|
||||
{
|
||||
int bkpt;
|
||||
char *token = strtok(arg_string, " ");
|
||||
|
||||
if (token == NULL) {
|
||||
printf("usage: breakpoint rm <bkpt num>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bkpt = strtol(token, NULL, 0);
|
||||
|
||||
if (bkpt < 0 || bkpt >= ARRAY_SIZE(breakpoints) || !breakpoints[bkpt].active) {
|
||||
printf("%d is not a valid breakpoint number\n", bkpt);
|
||||
return;
|
||||
}
|
||||
|
||||
breakpoints[bkpt].active = false;
|
||||
}
|
||||
|
||||
static void display_breakpoints(char *arg_string)
|
||||
{
|
||||
bool found1 = false;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(breakpoints); i++) {
|
||||
if (breakpoints[i].active) {
|
||||
printf("#%d: 0x%04x\n", i, breakpoints[i].addr);
|
||||
found1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found1) {
|
||||
printf("No breakpoints set\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void breakpoint(char *arg_list)
|
||||
{
|
||||
static bool init = false;
|
||||
static struct tri commands;
|
||||
|
||||
if (!init) {
|
||||
memset(breakpoints, 0, sizeof(breakpoints));
|
||||
tri_init(&commands);
|
||||
tri_add_string(&commands, "add", set_breakpoint);
|
||||
tri_add_string(&commands, "set", set_breakpoint);
|
||||
tri_add_string(&commands, "delete", delete_breakpoint);
|
||||
tri_add_string(&commands, "remove", delete_breakpoint);
|
||||
tri_add_string(&commands, "info", display_breakpoints);
|
||||
tri_add_string(&commands, "display", display_breakpoints);
|
||||
tri_add_string(&commands, "list", display_breakpoints);
|
||||
init = true;
|
||||
}
|
||||
|
||||
char * remainder;
|
||||
gbdb_cmd *cmd;
|
||||
bool ambiguous = false;
|
||||
remainder = strchr(arg_list, ' ');
|
||||
if (remainder != NULL) {
|
||||
remainder[0] = '\0';
|
||||
remainder++;
|
||||
}
|
||||
|
||||
cmd = tri_get_string_autocomplete(&commands, arg_list, &ambiguous);
|
||||
|
||||
if(ambiguous) {
|
||||
printf("ambiguous breakpoint command: '%s'\n", arg_list);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == NULL) {
|
||||
printf("unrecognized breakpoint command: '%s'\n", arg_list);
|
||||
return;
|
||||
}
|
||||
|
||||
cmd(remainder);
|
||||
}
|
||||
|
||||
static bool breakpoint_is_at_addr(uint16_t addr) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(breakpoints); i++) {
|
||||
if (breakpoints[i].active && addr == breakpoints[i].addr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cpu_at_breakpoint(void)
|
||||
{
|
||||
return breakpoint_is_at_addr(lr35902_get_reg_16(&cpu, LR35902_REG_PC));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct tri commands;
|
||||
char line_buffer[INPUT_MAX_LEN];
|
||||
char old_buffer[INPUT_MAX_LEN];
|
||||
|
||||
tri_init(&commands);
|
||||
tri_add_string(&commands, "step", step);
|
||||
tri_add_string(&commands, "run", run);
|
||||
tri_add_string(&commands, "regs", regs);
|
||||
tri_add_string(&commands, "stats", stats);
|
||||
tri_add_string(&commands, "exit", quit);
|
||||
tri_add_string(&commands, "quit", quit);
|
||||
tri_add_string(&commands, "load", load);
|
||||
tri_add_string(&commands, "bootrom", load_bootrom);
|
||||
tri_add_string(&commands, "mem", mem);
|
||||
tri_add_string(&commands, "dump", mem_dump);
|
||||
tri_add_string(&commands, "peek", peek);
|
||||
tri_add_string(&commands, "help", help);
|
||||
tri_add_string(&commands, "breakpoint", breakpoint);
|
||||
tri_add_string(&commands, "reset", init);
|
||||
|
||||
init();
|
||||
|
||||
while (1) {
|
||||
bool ambiguous;
|
||||
char *remainder;
|
||||
gbdb_cmd* cmd;
|
||||
char *cmd_string;
|
||||
|
||||
show_prompt();
|
||||
fgets(line_buffer, INPUT_MAX_LEN, stdin);
|
||||
if (line_buffer[0] != '\n') {
|
||||
cmd_string = line_buffer;
|
||||
} else {
|
||||
cmd_string = old_buffer;
|
||||
}
|
||||
strip_newline(cmd_string);
|
||||
remainder = strchr(cmd_string, ' ');
|
||||
if (remainder != NULL) {
|
||||
remainder[0] = '\0';
|
||||
remainder++;
|
||||
}
|
||||
|
||||
cmd = tri_get_string_autocomplete(&commands, cmd_string, &ambiguous);
|
||||
|
||||
if (ambiguous) {
|
||||
printf("ambiguous command: '%s'\n", cmd_string);
|
||||
} else if (cmd == NULL) {
|
||||
printf("unrecognized command: '%s'\n", cmd_string);
|
||||
} else {
|
||||
cmd(remainder);
|
||||
}
|
||||
|
||||
if (cmd_string == line_buffer) {
|
||||
strncpy(old_buffer, line_buffer, INPUT_MAX_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
20
src/apps/sample-test.c
Normal file
20
src/apps/sample-test.c
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <glib.h>
|
||||
|
||||
void test_fail(const void *data)
|
||||
{
|
||||
g_test_fail();
|
||||
}
|
||||
|
||||
void test_pass(const void *data)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
g_test_add_data_func("/pass", NULL, test_pass);
|
||||
g_test_add_data_func("/fail", NULL, test_fail);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
65
src/apps/tri-test.c
Normal file
65
src/apps/tri-test.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "tri.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TO_PTR(x) ((void *) (uintptr_t) (x))
|
||||
#define TO_INT(x) ((int) (uintptr_t) (x))
|
||||
|
||||
static void test_tri_get_string(void)
|
||||
{
|
||||
struct tri tri;
|
||||
|
||||
tri_init(&tri);
|
||||
|
||||
g_assert_cmpint(tri_add_string(&tri, "a", TO_PTR(1)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "ab", TO_PTR(2)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "abc", TO_PTR(3)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "af", TO_PTR(4)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "abd", TO_PTR(5)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "$a!0123~&.`", TO_PTR(6)), ==, 0);
|
||||
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "a")), ==, 1);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "ab")), ==, 2);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "abc")), ==, 3);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "af")), ==, 4);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "abd")), ==, 5);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "abz")), ==, 0);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "ag")), ==, 0);
|
||||
g_assert_cmpint(TO_INT(tri_get_string(&tri, "$a!0123~&.`")), ==, 6);
|
||||
|
||||
tri_free(&tri);
|
||||
}
|
||||
|
||||
static void test_tri_prefix_match(void)
|
||||
{
|
||||
struct tri tri;
|
||||
|
||||
tri_init(&tri);
|
||||
|
||||
g_assert_cmpint(tri_add_string(&tri, "pr", TO_PTR(1)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "pref", TO_PTR(2)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "Z", TO_PTR(3)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "prefix", TO_PTR(4)), ==, 0);
|
||||
g_assert_cmpint(tri_add_string(&tri, "blah", TO_PTR(5)), ==, 0);
|
||||
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "p")), ==, 0);
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pr")), ==, 1);
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pre")), ==, 1);
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "pref")), ==, 2);
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "prefix10")), ==, 4);
|
||||
g_assert_cmpint(TO_INT(tri_prefix_match(&tri, "asdfasdf")), ==, 0);
|
||||
|
||||
tri_free(&tri);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func("/tri/get_string", test_tri_get_string);
|
||||
g_test_add_func("/tri/prefix_match", test_tri_prefix_match);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
Reference in New Issue
Block a user