Various bug fixes to assembler, added more data pointers.

This commit is contained in:
Anachronaut
2026-08-13 23:41:22 -04:00
parent b50210d127
commit c2440ae5fa
38 changed files with 1028 additions and 236 deletions
+95 -31
View File
@@ -9,6 +9,7 @@
#include <ctype.h>
#include "Assm-util.h"
#include "assembly.h"
#include "../Emulator/cpu.h" // For DATA_POINTERS, so the CPU stays the one source of truth.
int debug = 0;
@@ -43,42 +44,105 @@ int checkIfKeyword(intermediateElement *currentElement) {
int checkIfInstruction(intermediateElement *currentElement) {
char token[32];
strncpy(token, currentElement->token, sizeof(token)-1); //copying over one less than the total size of the buffer ensures we wind up with a null terminated string.
toUppercase(token);
if (getOpcode(token) != 0xFE) {
// It's a valid instruction, save its value and set its type.
currentElement->type = INSTRUCTION;
currentElement->byteValue = getOpcode(token);
currentElement->byteLength = 1;
if (debug) printf("Token: %s is an instruction.\n", currentElement->token);
return 1;
if (strlen(currentElement->token) >= sizeof(token)) {
// Longer than any mnemonic could be, so it is not one.
return 0;
}
return 0;
strcpy(token, currentElement->token);
toUppercase(token);
// An instruction that works through a Data Pointer may name which one by
// hanging a selector off the mnemonic, as in LDA.2. Split that off before
// looking the mnemonic up.
char *selector = strchr(token, '.');
if (selector) {
*selector = '\0';
selector++;
}
uint8_t opcode = getOpcode(token);
if (opcode == 0xFE) {
// It's not an instruction.
return 0;
}
currentElement->type = INSTRUCTION;
currentElement->byteValue = opcode;
currentElement->byteLength = 1;
currentElement->dataPointer = 0;
if (instructionTakesDataPointer(opcode)) {
// The selector is emitted whether or not it was written, so these are
// always two bytes. Leaving it off is the same as writing 0.
currentElement->byteLength = 2;
if (selector) {
char *end;
long value = strtol(selector, &end, 10);
if (*selector == '\0' || *end != '\0' || value < 0 || value >= DATA_POINTERS) {
fprintf(stderr, RED "Error: \"%s\" does not name a Data Pointer.\n Selectors run from 0 to %d.\n" RESET, currentElement->token, DATA_POINTERS - 1);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
currentElement->dataPointer = (uint8_t)value;
}
} else if (selector) {
fprintf(stderr, RED "Error: %s does not work through a Data Pointer, so it cannot take a selector.\n" RESET, token);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
if (debug) printf("Token: %s is an instruction using Data Pointer %d.\n", currentElement->token, currentElement->dataPointer);
return 1;
}
int checkIfLiteralValue(intermediateElement *currentElement) {
char token[32];
strncpy(token, currentElement->token, sizeof(token)-1);
if (token[0] == '0') {
// It's a literal value. Check if it's hex or dec.
if(token[1] == 'x') {
// It's a hex literal. Set its value and type.
currentElement->type = VALUE;
currentElement->byteLength = 1;
memmove(token, token + 2, strlen(token)); // Shift the string over to get rid of the 0x.
currentElement->byteValue = (uint8_t)strtol(token, NULL, 16);
if (debug) printf("Token: %s is a hexadecimal literal. \n", currentElement->token);
} else if (token[1] == 'd') {
// It's a decimal literal. Set its value and type.
currentElement->type = VALUE;
currentElement->byteLength = 1;
memmove(token, token + 2, strlen(token)); // Shift the string over to get rid of the 0d.
currentElement->byteValue = (uint8_t)strtol(token, NULL, 10);
if (debug) printf("Token: %s is a decimal literal. \n", currentElement->token);
}
return 1;
const char *token = currentElement->token;
if (token[0] != '0') {
// It's not a literal value.
return 0;
}
return 0;
// A leading zero means the programmer was trying to write a literal, so anything
// malformed from here on is an error. Falling through to the label check instead
// would quietly emit the wrong number of bytes and shift the rest of the program.
int base;
const char *baseName;
if (token[1] == 'x') {
base = 16;
baseName = "hexadecimal";
} else if (token[1] == 'd') {
base = 10;
baseName = "decimal";
} else {
fprintf(stderr, RED "Error: Malformed literal value \"%s\".\n Literals must be prefaced with 0x for hexadecimal or 0d for decimal.\n" RESET, token);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
// Everything after the prefix has to be a digit in that base.
const char *digits = token + 2;
if (*digits == '\0') {
fprintf(stderr, RED "Error: Literal value \"%s\" has no digits after its prefix.\n" RESET, token);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
for (const char *c = digits; *c; c++) {
if (!(base == 16 ? isxdigit((unsigned char)*c) : isdigit((unsigned char)*c))) {
fprintf(stderr, RED "Error: \"%c\" is not a %s digit, in literal value \"%s\".\n" RESET, *c, baseName, token);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
}
// The digits are all valid, so the only thing left to get wrong is the range.
long value = strtol(digits, NULL, base);
if (value > 255) {
fprintf(stderr, RED "Error: Literal value \"%s\" is too large to fit in one byte.\n Values must be in the range 0x00 to 0xFF, or 0d0 to 0d255.\n" RESET, token);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
currentElement->type = VALUE;
currentElement->byteLength = 1;
currentElement->byteValue = (uint8_t)value;
if (debug) printf("Token: %s is a %s literal. \n", token, baseName);
return 1;
}
int checkIfLabel(intermediateElement *currentElement) {
+1
View File
@@ -48,6 +48,7 @@ typedef struct {
char* fileName;
int lineNumber;
uint8_t byteValue;
uint8_t dataPointer; // Which Data Pointer this instruction works through, if it works through one.
int byteLength;
uint16_t address;
int type; // "KEYWORD", "INSTRUCTION" , "LABEL" , "VALUE", "STRING"
+22
View File
@@ -82,6 +82,28 @@ const char* getMnemonic(uint8_t opcode) {
return "---";
}
int instructionTakesDataPointer(uint8_t opcode) {
// These instructions all work through a Data Pointer, and so are followed by a
// byte naming which one. Everything else is a single byte opcode as before.
switch (opcode) {
case 0x33: // PSHD
case 0x36: // POPD
case 0x40: // INCD
case 0x41: // DECD
case 0x42: // LDA
case 0x43: // LDB
case 0x44: // STQ
case 0x45: // STA
case 0x46: // STB
case 0x47: // SETD
case 0x48: // DPUP
case 0x49: // DPDN
return 1;
default:
return 0;
}
}
uint8_t getOpcode(char* mnemonic) {
for (int i = 0; i < num_instructions; i++) {
if (strcmp(instruction_set[i].mnemonic, mnemonic) == 0) {
+2
View File
@@ -12,4 +12,6 @@ const char* getMnemonic(uint8_t opcode);
uint8_t getOpcode(char* mnemonic);
int instructionTakesDataPointer(uint8_t opcode);
#endif // CPU_H
+64 -16
View File
@@ -13,6 +13,7 @@
#include <stdint.h>
#include "secondPass.h"
#include "Assm-util.h"
#include "assembly.h"
int debugSecondPass = 0;
@@ -103,6 +104,55 @@ void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize)
}
}
// Reports the type of the token following index i, or UNKNOWN if there isn't one.
// The operand checks go through this so that an instruction sitting at the very end of
// a program is reported as a missing operand instead of reading off the end of the array.
static int nextTokenType(intermediateElement *intermediateArray, int arraySize, int i) {
if (i + 1 >= arraySize) {
return UNKNOWN;
}
return intermediateArray[i + 1].type;
}
// Every instruction that reads operand bytes out of Program Memory needs those bytes to
// actually be there. If they aren't, the following instruction gets eaten as an operand
// and everything after it shifts, so these all have to be hard errors.
static void checkOperands(intermediateElement *intermediateArray, int arraySize, int i) {
uint8_t opcode = intermediateArray[i].byteValue;
int nextType = nextTokenType(intermediateArray, arraySize, i);
const char *problem = NULL;
if (((opcode & 0xF0) == 0x10) && (opcode != 0x1F)) {
// Branches and CALL take a two byte address, which only a label can supply.
if (nextType != LABEL) problem = "Branch without label.";
} else if ((opcode & 0xF0) == 0xD0 || (opcode & 0xF0) == 0xE0) {
// The instruction is either an input or output and must be followed by a value.
if (nextType != VALUE) problem = "I/O without destination port.";
} else if (opcode == 0x26 || opcode == 0x27) {
// INIA and INIB must be followed by the literal value to load.
if (nextType != VALUE) problem = "Immediate load without a value to load.";
} else if (opcode == 0x48 || opcode == 0x49) {
// DPUP and DPDN must be followed by the literal offset to apply.
if (nextType != VALUE) problem = "Data Pointer offset without an offset value.";
} else if (opcode == 0x47) {
// SETD takes a two byte address, as either a label or a pair of literal bytes.
if (nextType == VALUE) {
if (nextTokenType(intermediateArray, arraySize, i + 1) != VALUE) {
problem = "SETD given one literal byte, but an address is two bytes.";
}
} else if (nextType != LABEL) {
problem = "SETD without an address.";
}
}
if (problem) {
fprintf(stderr, RED "Error: %s\n" RESET, problem);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
printf("Token: %s\n", intermediateArray[i].token);
exit(1);
}
}
void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize, uint8_t *Program, int *programCount, uint8_t *Data, int *dataCount) {
for (int i = 0; i < arraySize; i++) {
if (intermediateArray[i].destination == PROGRAM) {
@@ -110,23 +160,13 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
case INSTRUCTION:
// Add instruction byte to Program buffer.
Program[(*programCount)++] = intermediateArray[i].byteValue;
// Check if it's a branch instruction.
if (((intermediateArray[i].byteValue & 0xF0) == 0x10) && (intermediateArray[i].byteValue != 0x1F)){
if (intermediateArray[i + 1].type != LABEL) {
fprintf(stderr, RED "Error: Branch without label.\n" RESET);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
exit(1);
}
} else if ((intermediateArray[i].byteValue & 0xF0) == 0xD0 || (intermediateArray[i].byteValue & 0xF0) == 0xE0) {
// The instruction is either an input or output and must be followed by a value
if (intermediateArray[i + 1].type != VALUE) {
fprintf(stderr, RED "Error: I/O without destination port.\n" RESET);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
printf("Token: %s\n", intermediateArray[i].token);
exit(1);
}
// Instructions that work through a Data Pointer carry a selector
// byte naming which one, whether or not the programmer wrote it.
if (instructionTakesDataPointer(intermediateArray[i].byteValue)) {
Program[(*programCount)++] = intermediateArray[i].dataPointer;
}
// Make sure any operand bytes this instruction expects are present.
checkOperands(intermediateArray, arraySize, i);
break;
case VALUE:
// Add literal value to Program buffer.
@@ -151,6 +191,14 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
}
Data[(*dataCount)++] = '\0'; // Add null terminator to Data buffer
break;
case LABEL:
// The label table reserved two bytes for this, but there's nothing here
// that knows how to emit them, so every later Data label would be shifted
// out of place. Refuse it rather than assemble something that looks fine.
fprintf(stderr, RED "Error: Label \"%s\" used as a value in the Data Segment.\n Label references are only supported in the Program Segment.\n" RESET, intermediateArray[i].token);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
exit(1);
break;
}
}
}