meow.git

instruct.c

espurr
/* meow - 6502 assembler
 * Copyright (C) 2024-2025 ArcNyxx
 * see LICENCE file for licensing information */

#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>

#include "instruct.h"
#include "label.h"
#include "meow.h"
#include "tables.h"

static const char *modes[] = {
	"implied",
	"absolute",
	"absolute indexed with x",
	"absolute indexed with y",
	"absolute indirect",
	"immediate",
	"zero page",
	"zero page indexed with x",
	"zero page indexed with y",
	"zero page indexed with x indirect",
	"zero page indirect indexed with y"
};

extern char ps[0xffff];
extern uint16_t pc;

static addr_t
argument(char **arg)
{
	if ((*arg) == NULL)
		return IMP;

	if ((*arg)[0] == '#') {
		++(*arg);
		return IMM;
	}

	if ((*arg)[0] == '!') {
		char *chr = strchr(*arg + 1, ',');
		if (chr == NULL) {
			++(*arg);
			return ZP;
		}

		addr_t addr;
		if (toupper(chr[1]) == 'X')
			addr = ZPX;
		else if (toupper(chr[1]) == 'Y')
			addr = ZPY;
		else
			return INVADDR;
		if (chr[2] != '\0' || chr == *arg + 1)
			return INVADDR;

		++(*arg);
		chr[0] = '\0';
		return addr;
	}

	if ((*arg)[0] == '(') {
		if ((*arg)[1] != '!') {
			char *chr = strchr((*arg) + 1, ')');
			if (chr == NULL || chr[1] != '\0' || chr == *arg + 1)
				return INVADDR;

			++(*arg);
			chr[0] = '\0';
			return ABSI;
		}

		char *chr = strchr((*arg) + 2, ')');
		addr_t addr;
		if (chr == NULL || chr == (*arg) + 2)
			return INVADDR;
		if (toupper(chr[-1]) == 'X' && chr[-2] == ',' &&
				chr[1] == '\0') {
			addr = ZPXI;
			chr[-2] = '\0';
		} else if (chr[1] == ',' && toupper(chr[2]) == 'Y' &&
				chr[3] == '\0') {
			addr = ZPYI;
			chr[0] = '\0';
		} else {
			return INVADDR;
		}

		*arg += 2;
		return addr;
	}

	char *chr = strchr((*arg), ',');
	if (chr == NULL)
		return ABS;

	addr_t addr;
	if (toupper(chr[1]) == 'X')
		addr = ABSX;
	else if (toupper(chr[1]) == 'Y')
		addr = ABSY;
	else
		return INVADDR;
	if (chr[2] != '\0' || chr == *arg)
		return INVADDR;

	chr[0] = '\0';
	return addr;
}

void
instruct(char *first)
{
	char *second = strtok(NULL, " \t\n\r");
	if (second != NULL && strtok(NULL, " \t\n\r") != NULL)
		meow("warn: instruction followed by extraneous information");

	mne_t mne;
	if ((mne = mnemonic(first)) == INVMNE) {
		meow("error: invalid instruction mnemonic: %s", first);
		return;
	}
	addr_t addr;
	if ((addr = argument(&second)) == INVADDR) {
		meow("error: invalid argument format: %s", second);
		return;
	}
	if (addr == ABS && (mne >= BCC && mne <= BVS))
		addr = REL; /* determined based on instruction */
	uint8_t code;
	if ((code = opcode(mne, addr)) == 0xff) {
		meow("error: invalid instruction: %s using addressing mode %s",
				first, modes[addr]);
		return;
	}

	ps[pc++] = code;

	if (addr == IMP)
		return;

	asiz_t type = BIT8;
	if (addr == REL)
		type = RELS;
	else if (addr >= ABS && addr <= ABSI)
		type = BIT16;

	if (addr != IMM && islabel(second)) {
		labeladd(second, pc, type);
		pc += 1 + (type == BIT16);
		return;
	}

	char *endptr;
	long num = strnum(second, &endptr);
	if (endptr[0] != '\0')
		meow("warn: number invalid: %s", second);

	if (type != BIT16) {
		if (errno != 0 || num > SCHAR_MAX || num < SCHAR_MIN)
			meow("warn: number is out of 8-bit range: %s", second);
		ps[pc++] = (int8_t)num;
	} else {
		if (errno != 0 || num > SHRT_MAX || num < SHRT_MIN)
			meow("warn: number is out of 16-bit range: %s", second);
		int16_t snum = num;
		ps[pc++] = snum & 0xff;
		ps[pc++] = (snum & 0xff00) >> 2;
	}
}