Files
AnachronautandClaude Opus 5 7dca141aae The vector's number goes beside its kind, not at the end of the line
Kind and Number together are what names a thing: Vector 16 and Device 32
are identifiers in a way that Vector on its own is not. Everything after
them - where it lives, what it is called, where it was written - says
something about it rather than naming it, so with the number at the far
end a line opened with a bare kind and closed with the fact that would
have told you what you were reading.

Fields are now Kind, Number, Address, Name, File, Line. A label still
carries a dash where its number would be, which reads as "this kind is
not numbered" rather than as a field that went missing.

Manual and the docs check follow. Verified with break.sh by swapping the
number and the address back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-05 11:03:11 -04:00

935 lines
44 KiB
C

// secondPass.c
// Functions for the 'second pass' of the SplitBit Assembler.
// The goal here is to resolve the addresses of labels.
// We'll want to abort if the program comes out to greater than the maximum memory for SplitBit.
// We'll also want to abort if there's a label used with no definition.
// Written by Anachronaut
// 10/25/2024
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdint.h>
#include "secondPass.h"
#include "Assm-util.h"
#include "assembly.h"
#include "sbex.h"
int debugSecondPass = 0;
Label labelArray[MAX_LABELS];
int labelCount = 0;
// Beside the labels rather than beside the code that fills it, because the symbol file
// below reads both tables and this one would otherwise not be declared yet.
VectorEntry vectorArray[MAX_VECTORS];
int vectorArrayCount = 0;
// ---- Where everything ended up ----
//
// Every label with the memory and address it was given and the place it was written, one
// per line, fields separated by tabs. The assembler knows all of this and nothing else
// does: a program on the disk is bytes, and the machine's own monitor can disassemble it
// but has no idea what any of it is called or where it came from.
//
// WHAT IT IS FOR is two questions. The first is where a program spends its time - counting
// which addresses get called says a great deal and names nothing, so the answer arrives as
// a list of numbers and somebody has to work out by hand which routine each one is inside.
// The second is where a name lives, which matters more the bigger the program gets: CosmOS
// and its libraries define over a thousand names across a dozen files, and finding the one
// definition of a routine means grepping for it and reading past every place it is called.
// The file and line answer that outright.
//
// TABS, so that the file is a table every ordinary tool already understands - cut -f3, awk
// -F'\t', sort -k1,2 - and so that a name never has to be quoted. No header line, for the
// same reason: nothing that reads it should have to know to skip one.
//
// THE KIND IS THE FIRST FIELD BECAUSE THIS IS A HARVARD MACHINE. Program and Data are
// separate address spaces, so 0x3000 is two different places and an address alone does not
// say which. That is invisible in a loadable program, where the two segments are based
// somewhere apart, and immediate in a boot image, where both start at zero and every
// address in the file appears twice.
//
// VECTORS ARE IN HERE TOO, and they are the part nothing else can tell you. A pinned vector
// has its number written in the source that pins it, but an automatic one is handed a number
// by this assembler and that number appears nowhere at all - not in the source, not in the
// binary in any form a reader can find. A vector also costs two hops to follow by hand: the
// name in "SWI osFileRead" is not the name of the routine that implements it, so finding the
// code means grepping for the vector, reading the handler's name off the Vector Segment, and
// grepping again. A vector row gives its number, the handler's name, and the line the two
// were tied together on.
//
// A VECTOR CARRIES BOTH ITS NUMBERS, which is why there is a sixth field. Its number is what
// a program writes and what the machine dispatches on; its slot is where the handler's
// address is stored in Program Memory, base + number * 2, which is what the loader writes
// and what a memory dump shows. Neither can be worked out from the other without knowing
// which table it is in, so the file says both.
//
// THE NUMBER SITS SECOND, beside the kind rather than out at the end, because the two of
// them together are what names the thing: "Vector 26" and "Device 32" are single identifiers
// in the way "Vector" alone is not. Everything after them - where it lives, what it is
// called, where it was written - is something about the thing rather than part of naming it,
// so the line reads as an identity followed by its properties. A label has no number and
// carries a dash in that column, which is the shape of the answer "this kind is not numbered"
// rather than an empty space that could be a missing field.
//
// Sorted by kind and then address rather than by name, because the question asked of it is
// always "what is at this address"; sorting by address alone would interleave separate
// address spaces and make the first column flicker between them. The table is small enough
// that sorting it is free.
// The four kinds, in the order they are listed. Program and Data are the two memories, then
// the two vector tables.
#define ROW_PROGRAM 0
#define ROW_DATA 1
#define ROW_VECTOR 2
#define ROW_DEVICE 3
typedef struct {
int kind;
uint16_t address; // Where it lives: a label's address, or a vector's slot.
const char *name;
const char *fileName;
int lineNumber;
int number; // The vector or port number, or -1 for a label, which has none.
} SymbolRow;
static int byPlace(const void *left, const void *right) {
const SymbolRow *a = left, *b = right;
if (a->kind != b->kind) {
return a->kind < b->kind ? -1 : 1;
}
if (a->address != b->address) {
return a->address < b->address ? -1 : 1;
}
return strcmp(a->name, b->name);
}
void writeSymbolFile(const char *path) {
FILE *file = fopen(path, "w");
if (!file) {
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, path);
exit(1);
}
int rowCount = labelCount + vectorArrayCount;
SymbolRow *rows = malloc((size_t)rowCount * sizeof(SymbolRow) + 1);
if (!rows) {
fprintf(stderr, RED "Error: Out of memory writing the symbol file.\n" RESET);
fclose(file);
exit(1);
}
int n = 0;
for (int i = 0; i < labelCount; i++) {
// A label is put in one of the two segments by populateLabelTable and there is no
// third, but the kind is taken from the type rather than assumed, so a label that
// somehow arrived as neither is not quietly filed under Data.
rows[n].kind = labelArray[i].type == PROGRAM ? ROW_PROGRAM : ROW_DATA;
rows[n].address = labelArray[i].address;
rows[n].name = labelArray[i].label;
rows[n].fileName = labelArray[i].fileName;
rows[n].lineNumber = labelArray[i].lineNumber;
rows[n].number = -1;
n++;
}
for (int i = 0; i < vectorArrayCount; i++) {
int hardware = vectorArray[i].base == HARDWARE_VECTOR_BASE;
rows[n].kind = hardware ? ROW_DEVICE : ROW_VECTOR;
// The same arithmetic the loader is given, so that what this says a vector's slot is
// and what actually gets written there cannot drift apart.
rows[n].address = vectorArray[i].base
+ (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
// A device has no name of its own - it is named by the port it is plugged into - so
// it is listed under its handler, which is the only name it has.
rows[n].name = vectorArray[i].name ? vectorArray[i].name
: vectorArray[i].handlerName ? vectorArray[i].handlerName
: "?";
rows[n].fileName = vectorArray[i].fileName;
rows[n].lineNumber = vectorArray[i].lineNumber;
rows[n].number = vectorArray[i].index;
n++;
}
qsort(rows, (size_t)n, sizeof(SymbolRow), byPlace);
static const char *kindName[4] = { "Program", "Data", "Vector", "Device" };
for (int i = 0; i < n; i++) {
char number[12]; // Wide enough for any int, which is more than a vector needs.
if (rows[i].number < 0) {
// A label has no number, and the field says so rather than being left empty: a
// run of tabs with nothing between them is the one thing a reader of this file,
// human or otherwise, can miscount.
snprintf(number, sizeof(number), "-");
} else {
snprintf(number, sizeof(number), "%d", rows[i].number);
}
fprintf(file, "%s\t%s\t%04X\t%s\t%s\t%d\n",
kindName[rows[i].kind], number, rows[i].address, rows[i].name,
rows[i].fileName ? rows[i].fileName : "?",
rows[i].lineNumber);
}
free(rows);
fclose(file);
}
void freeLabelList() {
for (int i = 0; i < labelCount; i++) {
if (labelArray[i].label) {
free(labelArray[i].label);
}
}
labelCount = 0;
}
void addLabel(char *labelName, uint16_t address, int type, const char *fileName, int lineNumber) {
if (labelCount < MAX_LABELS) {
// Duplicate labelName and remove the trailing colon, if present
char *cleanedLabel = strdup(labelName);
int len = strlen(cleanedLabel);
if (cleanedLabel[len - 1] == ':') {
cleanedLabel[len - 1] = '\0'; // Remove the colon
}
// A NAME THAT IS ALREADY AN INSTRUCTION IS REFUSED, and the error is here rather
// than at the branch that could not find it. Mnemonics are matched with the token
// uppercased, so a label called "wait" and the instruction WAIT are the same word
// - and when WAIT was added, Keys.asm had been using that label for a year. What
// it reported was "Branch without label" at the BRQ, thirty lines from the cause
// and naming nothing that had changed.
//
// This will happen again. Every instruction added takes a word out of the space
// of label names, so the check belongs where the name is claimed.
{
char upper[8];
int n = 0;
for (; cleanedLabel[n] != '\0' && n < (int)sizeof(upper) - 1; n++) {
upper[n] = (char)toupper((unsigned char)cleanedLabel[n]);
}
upper[n] = '\0';
// Only a name short enough to BE a mnemonic can collide with one, and the
// longest is four characters. A longer name is truncated by the loop above
// and would not match anything, which is the right answer.
if (cleanedLabel[n] == '\0' && getOpcode(upper) != NOT_AN_OPCODE) {
fprintf(stderr, RED "Error: \"%s\" is an instruction, so it cannot also be"
" a label.\n" RESET, cleanedLabel);
printf("File: %s at line %d.\n", fileName, lineNumber);
free(cleanedLabel);
exit(1);
}
}
// A name may only be defined once. Without this check a reference quietly
// resolves to whichever definition came first, so a typo or a name that two
// libraries both happen to use is very hard to track down.
for (int i = 0; i < labelCount; i++) {
if (strcmp(labelArray[i].label, cleanedLabel) == 0) {
fprintf(stderr, RED "Error: Label \"%s\" is defined more than once.\n" RESET, cleanedLabel);
printf("File: %s at line %d.\n", fileName, lineNumber);
free(cleanedLabel);
exit(1);
}
}
labelArray[labelCount].label = cleanedLabel;
labelArray[labelCount].address = address;
labelArray[labelCount].type = type;
labelArray[labelCount].fileName = fileName;
labelArray[labelCount].lineNumber = lineNumber;
if (debugSecondPass) printf("Added label %s with address %04X\n", labelName, labelArray[labelCount].address);
labelCount++;
} else {
fprintf(stderr, RED "Error: Too many labels. This program and everything it\n"
" includes may define %d between them, and \"%s\" is one too many.\n" RESET,
MAX_LABELS, labelName);
printf("File: %s at line %d.\n", fileName, lineNumber);
exit(1);
}
}
void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
// Counting starts at the base, so a label in a program built to live somewhere else
// already holds the address it will have once it is there. Nothing relocates
// anything, which is exactly why this has to be right at assembly time.
int programCount = segmentBase(PROGRAM);
int dataCount = segmentBase(DATA);
// Loop through the array, if there's a label definition, add it to the label list.
for (int i = 0; i < arraySize ; i++) {
// How many zeroes an #Align comes to depends on where the cursor has reached,
// so it can only be worked out here, walking the tokens in order. It has to be
// settled before the running count moves past it, or every label after it lands
// in the wrong place.
if (intermediateArray[i].type == ALIGNMENT) {
int cursor = (intermediateArray[i].destination == PROGRAM) ? programCount : dataCount;
int alignment = intermediateArray[i].address;
intermediateArray[i].byteLength = (alignment - (cursor % alignment)) % alignment;
}
if (intermediateArray[i].type == LABEL_DEFINITION && intermediateArray[i].destination != VECTORS) {
if (intermediateArray[i].destination == PROGRAM) {
addLabel(intermediateArray[i].token, (uint16_t)programCount, PROGRAM,
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
} else {
addLabel(intermediateArray[i].token, (uint16_t)dataCount, DATA,
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
}
}
if (intermediateArray[i].destination == PROGRAM) {
programCount += intermediateArray[i].byteLength;
} else if (intermediateArray[i].destination == DATA) {
dataCount += intermediateArray[i].byteLength;
}
if (debugSecondPass) printf("Token: %s with byte length %d to destination %d of type %d\n", intermediateArray[i].token ,intermediateArray[i].byteLength, intermediateArray[i].destination, intermediateArray[i].type);
}
if (programCount > PROGRAM_SEGMENT_LIMIT ) {
fprintf(stderr, RED "Error: Program is too long to fit in Program Memory.\n"
" The Program Segment may not run past 0x%04X, where the vector table begins.\n" RESET,
PROGRAM_SEGMENT_LIMIT - 1);
exit(1);
}
if (dataCount > 0xFFFF ) {
fprintf(stderr, RED "Error: Data is too long to fit in Data Memory.\n" RESET);
exit(1);
}
}
// ---- The Vector Segment ----
int findLabelAddress(const char *labelName);
int vectorCount() {
return vectorArrayCount;
}
void freeVectorList() {
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].name) {
free(vectorArray[i].name);
}
if (vectorArray[i].handlerName) {
free(vectorArray[i].handlerName);
}
}
vectorArrayCount = 0;
}
// The words the Vector Segment understands. These are spelled without regard to case,
// the way mnemonics are, because they are part of the language rather than names the
// programmer chose.
static int sameWord(const char *a, const char *b) {
while (*a && *b) {
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) {
return 0;
}
a++;
b++;
}
return *a == *b;
}
// The vectors that already mean something. Everything else a program names is numbered
// for it, starting above the range held back for faults.
static const struct {
const char *name;
uint8_t index;
} reservedVectors[] = {
{ "Boot", VECTOR_BOOT },
{ "SoftReset", VECTOR_SOFT_RESET },
{ "BadOpcode", VECTOR_INVALID_OPCODE },
{ "GuardViolation", VECTOR_GUARD_VIOLATION },
{ "BankFault", VECTOR_BANK_FAULT },
{ "NoHandler", VECTOR_NO_HANDLER },
{ "NoDevice", VECTOR_NO_DEVICE },
};
static const int reservedVectorCount = (int)(sizeof(reservedVectors) / sizeof(reservedVectors[0]));
static void vectorError(const char *message, intermediateElement *element) {
fprintf(stderr, RED "Error: %s\n" RESET, message);
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
printf("Token: %s\n", element->token);
exit(1);
}
// Whether the token at b is written on the same line as the one at a, and so belongs to
// the same entry. A line is what tells a name with a handler apart from a name on its own.
static int sameLine(intermediateElement *intermediateArray, int a, int b) {
if (a < 0 || b < 0) {
return 0;
}
return intermediateArray[a].lineNumber == intermediateArray[b].lineNumber
&& intermediateArray[a].fileName == intermediateArray[b].fileName;
}
// The next token belonging to the Vector Segment, or -1 if the segment has run out.
static int nextVectorToken(intermediateElement *intermediateArray, int arraySize, int from) {
for (int i = from; i < arraySize; i++) {
if (intermediateArray[i].destination == VECTORS && intermediateArray[i].type != KEYWORD) {
return i;
}
}
return -1;
}
// A declared vector has a name and a number but no handler, so nothing goes into the
// table for it. It exists so that a program can name a service it calls without claiming
// to implement it, which is what lets one file be included by both sides.
// Where a vector of this name already is, or -1. A name can be met twice: once where it
// is declared and once where somebody supplies its handler.
static int findVector(const char *name) {
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].name && strcmp(vectorArray[i].name, name) == 0) {
return i;
}
}
return -1;
}
static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler,
int declaredOnly, intermediateElement *element,
const char *handlerName) {
if (vectorArrayCount >= MAX_VECTORS) {
vectorError("Too many vectors defined.", element);
}
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].index == index && vectorArray[i].base == base
&& !vectorArray[i].declaredOnly && !declaredOnly) {
fprintf(stderr, RED "Error: That vector already has a handler.\n" RESET);
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
exit(1);
}
if (name && vectorArray[i].name && strcmp(vectorArray[i].name, name) == 0) {
fprintf(stderr, RED "Error: Vector \"%s\" is named more than once.\n" RESET, name);
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
exit(1);
}
}
vectorArray[vectorArrayCount].name = name ? strdup(name) : NULL;
vectorArray[vectorArrayCount].index = index;
vectorArray[vectorArrayCount].base = base;
vectorArray[vectorArrayCount].handler = handler;
vectorArray[vectorArrayCount].declaredOnly = declaredOnly;
vectorArray[vectorArrayCount].handlerName = handlerName ? strdup(handlerName) : NULL;
vectorArray[vectorArrayCount].fileName = element->fileName;
vectorArray[vectorArrayCount].lineNumber = element->lineNumber;
vectorArrayCount++;
}
// Resolves the handler named by the token at the given index.
static uint16_t resolveHandler(intermediateElement *intermediateArray, int at, const char *what) {
if (at < 0) {
fprintf(stderr, RED "Error: %s is not followed by a handler to go to.\n" RESET, what);
exit(1);
}
if (intermediateArray[at].type != LABEL) {
vectorError("A vector's handler has to be named by a label.", &intermediateArray[at]);
}
int address = findLabelAddress(intermediateArray[at].token);
if (address == -1) {
vectorError("That handler does not exist.", &intermediateArray[at]);
}
return (uint16_t)address;
}
// Is anything already using this software vector number?
static int vectorNumberTaken(uint8_t index) {
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE && vectorArray[i].index == index) {
return 1;
}
}
return 0;
}
void populateVectorTable(intermediateElement *intermediateArray, int arraySize) {
// Software vectors a program names for itself and does not pin are numbered in the
// order they are written, from the automatic range. A programmer never types one of
// these, so there is no way to land on a reserved vector or on somebody else's
// agreed number by accident.
int nextFreeVector = VECTOR_FIRST_AUTO;
int i = nextVectorToken(intermediateArray, arraySize, 0);
while (i >= 0) {
char *token = intermediateArray[i].token;
if (sameWord(token, "Device")) {
// A device is named by the port it is plugged into, because that is what
// decides which vector it arrives through. There is nothing to allocate.
int portToken = nextVectorToken(intermediateArray, arraySize, i + 1);
if (portToken < 0 || intermediateArray[portToken].type != VALUE) {
vectorError("Device has to say which port, as a number.", &intermediateArray[i]);
}
int handlerToken = nextVectorToken(intermediateArray, arraySize, portToken + 1);
uint16_t handler = resolveHandler(intermediateArray, handlerToken, "Device");
addVector(NULL, intermediateArray[portToken].byteValue, HARDWARE_VECTOR_BASE,
handler, 0, &intermediateArray[i],
intermediateArray[handlerToken].token);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
// A number on the same line, between the name and any handler, PINS the vector.
// Nothing else can appear there: a label may not begin with a digit, so a value in
// this position is a number and can be nothing else.
int after = nextVectorToken(intermediateArray, arraySize, i + 1);
int pinned = -1;
if (sameLine(intermediateArray, i, after) && intermediateArray[after].type == VALUE) {
pinned = intermediateArray[after].byteValue;
after = nextVectorToken(intermediateArray, arraySize, after + 1);
}
int handlerToken = after;
int hasHandler = sameLine(intermediateArray, i, handlerToken);
int already = findVector(token);
if (already >= 0) {
// Met before. A handler now is somebody implementing what was declared
// earlier, which is how one shared file can serve both sides.
//
// A number here has to be the one it was declared with. Saying it again is
// allowed, because an implementer repeating what it is implementing is
// harmless; saying a different one means the two sides disagree about which
// vector this is, which is the whole thing pinning exists to prevent.
if (pinned >= 0 && pinned != vectorArray[already].index) {
vectorError("That vector was already given a different number.",
&intermediateArray[i]);
}
if (!hasHandler) {
vectorError("That vector is declared more than once.", &intermediateArray[i]);
}
if (!vectorArray[already].declaredOnly) {
vectorError("That vector already has a handler.", &intermediateArray[i]);
}
vectorArray[already].handler = resolveHandler(intermediateArray, handlerToken, token);
vectorArray[already].declaredOnly = 0;
// The declaration said what it is called; this says where it was implemented,
// which is the more useful of the two places to be sent.
free(vectorArray[already].handlerName);
vectorArray[already].handlerName = strdup(intermediateArray[handlerToken].token);
vectorArray[already].fileName = intermediateArray[i].fileName;
vectorArray[already].lineNumber = intermediateArray[i].lineNumber;
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
uint8_t index;
int reserved = 0;
for (int r = 0; r < reservedVectorCount; r++) {
if (sameWord(token, reservedVectors[r].name)) {
index = reservedVectors[r].index;
reserved = 1;
break;
}
}
if (reserved && pinned >= 0) {
// Boot, SoftReset and BadOpcode are where the machine looks, not where a
// program says to look, so their numbers are not anybody's to choose.
vectorError("That vector's number is fixed by the machine and cannot be given.",
&intermediateArray[i]);
}
if (!reserved && pinned >= 0) {
if (pinned < VECTOR_FIRST_PINNED || pinned >= VECTOR_FIRST_AUTO) {
fprintf(stderr, RED "Error: %d is not a vector number that can be given.\n"
" Numbers below %d belong to the machine, and %d and up are\n"
" handed out by the assembler. A vector two programs have to\n"
" agree about takes one from %d to %d.\n" RESET,
pinned, VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO,
VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO - 1);
printf("File: %s at line %d.\n",
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
exit(1);
}
if (vectorNumberTaken((uint8_t)pinned)) {
vectorError("Another vector already has that number.", &intermediateArray[i]);
}
index = (uint8_t)pinned;
} else if (!reserved) {
if (nextFreeVector > 255) {
vectorError("There are no software vectors left to give this one.", &intermediateArray[i]);
}
index = (uint8_t)nextFreeVector;
nextFreeVector++;
}
if (!hasHandler) {
// Nothing follows it on the line, so this says what the vector is called and
// what number it has, and leaves implementing it to somebody else.
addVector(token, index, SOFTWARE_VECTOR_BASE, 0, 1, &intermediateArray[i], NULL);
i = handlerToken;
continue;
}
uint16_t handler = resolveHandler(intermediateArray, handlerToken, token);
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, 0, &intermediateArray[i],
intermediateArray[handlerToken].token);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
}
}
void fillInVectorReferences(intermediateElement *intermediateArray, int arraySize) {
for (int i = 0; i < arraySize; i++) {
if (intermediateArray[i].type != VECTOR_REFERENCE) {
continue;
}
int found = 0;
for (int v = 0; v < vectorArrayCount; v++) {
if (vectorArray[v].name && strcmp(vectorArray[v].name, intermediateArray[i].token) == 0) {
if (vectorArray[v].base != SOFTWARE_VECTOR_BASE) {
vectorError("SWI can only reach a software vector.", &intermediateArray[i]);
}
intermediateArray[i].byteValue = vectorArray[v].index;
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, RED "Error: \"%s\" is not a vector.\n Names used with SWI have to be given a handler in a #Vectors section.\n" RESET,
intermediateArray[i].token);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
exit(1);
}
}
}
int findLabelAddress(const char *labelName) {
for (int i = 0; i < labelCount; i++) {
if (strcmp(labelArray[i].label, labelName) == 0) {
return labelArray[i].address;
}
}
return -1; // Label not found
}
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize) {
for (int i = 0; i < arraySize; i++) {
// The Vector Segment is resolved separately. Most of what it holds is not a
// label at all: the words that name a vector are the segment's own, and looking
// them up here would report them as undefined.
if (intermediateArray[i].destination == VECTORS) {
continue;
}
if (intermediateArray[i].type == LABEL) {
// Look up the label in the label table
int address = findLabelAddress(intermediateArray[i].token);
if (address == -1) {
fprintf(stderr, RED "Error: Undefined label \"%s\".\n" RESET, intermediateArray[i].token);
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
exit(1);
}
// Assign the found address to the element
intermediateArray[i].address = address;
}
}
}
// 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;
// Listed rather than matched on the high nibble, because not every instruction in
// these two blocks takes an address: RET has none, and BRD gets its destination from
// a Data Pointer instead of from the program.
if (opcode == 0x60 || opcode == 0x61 || opcode == 0x62 ||
opcode == 0x63 || opcode == 0x64 || opcode == 0x71 ||
opcode == 0x66 || opcode == 0x67 || opcode == 0x68 || opcode == 0x69) {
// 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 == 0x72) {
// SWI names a vector, either by the name it was given in the Vector Segment or,
// rarely, as a literal number. Without one it swallows whatever follows it and
// every address after that shifts.
if (nextType != VECTOR_REFERENCE && nextType != VALUE) problem = "SWI without a vector to go to.";
} 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) {
switch (intermediateArray[i].type) {
case INSTRUCTION:
// Add instruction byte to Program buffer.
Program[(*programCount)++] = intermediateArray[i].byteValue;
// Instructions that work through a Data Pointer carry a selector
// byte naming which one, whether or not the programmer wrote it.
for (int d = 0; d < dataPointerOperands(intermediateArray[i].byteValue); d++) {
Program[(*programCount)++] = intermediateArray[i].dataPointer[d];
}
// Make sure any operand bytes this instruction expects are present.
checkOperands(intermediateArray, arraySize, i);
break;
case VALUE:
// Add literal value to Program buffer.
Program[(*programCount)++] = intermediateArray[i].byteValue;
break;
case VECTOR_REFERENCE:
// A vector is a number rather than a place, so this is one byte
// where a label would be two.
Program[(*programCount)++] = intermediateArray[i].byteValue;
break;
case PADDING:
case ALIGNMENT:
for (int z = 0; z < intermediateArray[i].byteLength; z++) {
Program[(*programCount)++] = 0x00;
}
break;
case LABEL:
// Split 16-bit label address into high and low bytes.
Program[(*programCount)++] = (intermediateArray[i].address >> 8) & 0xFF; // High byte
Program[(*programCount)++] = intermediateArray[i].address & 0xFF; // Low byte
break;
}
} else if (intermediateArray[i].destination == DATA) {
switch (intermediateArray[i].type) {
case VALUE:
// Add literal value to Data buffer.
Data[(*dataCount)++] = intermediateArray[i].byteValue;
break;
case STRING:
// Copy string literal to Data buffer, including null terminator.
for (int j = 0; intermediateArray[i].token[j] != '\0'; j++) {
Data[(*dataCount)++] = intermediateArray[i].token[j];
}
Data[(*dataCount)++] = '\0'; // Add null terminator to Data buffer
break;
case LABEL:
// A label named in the Data Segment puts its address there, which is
// how a program lays down a table of addresses for LDD to walk.
// Two bytes, most significant first, the same order addresses are
// stored in everywhere else.
Data[(*dataCount)++] = (intermediateArray[i].address >> 8) & 0xFF; // High byte
Data[(*dataCount)++] = intermediateArray[i].address & 0xFF; // Low byte
break;
case PADDING:
case ALIGNMENT:
for (int z = 0; z < intermediateArray[i].byteLength; z++) {
Data[(*dataCount)++] = 0x00;
}
break;
}
}
}
}
// A loadable program: sixteen bytes saying where it belongs, then the code and the data.
// The space below each base is not written out, because nothing needs to carry it: the
// header says where the bytes go and the loader puts them there.
static void writeLoadable(const char *outputFileName, uint8_t *Program, int programCount,
uint8_t *Data, int dataCount) {
uint16_t codeBase = segmentBase(PROGRAM);
uint16_t dataBase = segmentBase(DATA);
int codeLength = programCount - codeBase;
int dataLength = dataCount - dataBase;
if (codeLength < 0) codeLength = 0;
if (dataLength < 0) dataLength = 0;
FILE *outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName);
exit(1);
}
// Boot says where a program begins, which is what the entry field holds, so in a
// loadable program that line fills it in. It is NOT installed as vector 0: that entry
// is where the whole machine starts, and a program being loaded into a running system
// has no business saying anything about that.
//
// Without a Boot line the entry is the first byte of the code, which is where a
// program begins if it does not say otherwise.
uint16_t entry = codeBase;
int installed = 0;
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE
&& vectorArray[i].index == VECTOR_BOOT) {
entry = vectorArray[i].handler;
continue;
}
installed++;
}
if (installed > 255) {
fprintf(stderr, RED "Error: A loadable program may bring at most 255 vectors.\n" RESET);
exit(1);
}
uint8_t header[SBEX_HEADER_BYTES];
memset(header, 0, sizeof(header));
memcpy(header, SBEX_MAGIC, SBEX_MAGIC_BYTES);
// A program that brings vectors needs something of its loader that a version one
// loader does not know how to give, so it says so, and an older one refuses it rather
// than running it without them.
header[SBEX_VERSION_AT] = installed > 0 ? SBEX_VERSION_VECTORS : SBEX_VERSION;
header[SBEX_VECTORS_AT] = (uint8_t)installed;
header[SBEX_CODE_AT] = (uint8_t)(codeBase >> 8);
header[SBEX_CODE_AT + 1] = (uint8_t)(codeBase & 0xFF);
header[SBEX_ENTRY_AT] = (uint8_t)(entry >> 8);
header[SBEX_ENTRY_AT + 1] = (uint8_t)(entry & 0xFF);
header[SBEX_CODE_LEN_AT] = (uint8_t)(codeLength >> 8);
header[SBEX_CODE_LEN_AT + 1] = (uint8_t)(codeLength & 0xFF);
header[SBEX_DATA_AT] = (uint8_t)(dataBase >> 8);
header[SBEX_DATA_AT + 1] = (uint8_t)(dataBase & 0xFF);
header[SBEX_DATA_LEN_AT] = (uint8_t)(dataLength >> 8);
header[SBEX_DATA_LEN_AT + 1] = (uint8_t)(dataLength & 0xFF);
fwrite(header, 1, sizeof(header), outputFile);
fwrite(Program + codeBase, 1, (size_t)codeLength, outputFile);
fwrite(Data + dataBase, 1, (size_t)dataLength, outputFile);
// The vectors, last, so that everything before them sits where a version one loader
// already expects to find it.
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE
&& vectorArray[i].index == VECTOR_BOOT) {
continue;
}
uint16_t slot = vectorArray[i].base + (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
fputc((slot >> 8) & 0xFF, outputFile);
fputc(slot & 0xFF, outputFile);
fputc((vectorArray[i].handler >> 8) & 0xFF, outputFile);
fputc(vectorArray[i].handler & 0xFF, outputFile);
}
fclose(outputFile);
printf("Successfully wrote SplitBit loadable program to \"%s\".\n", outputFileName);
printf(GREEN " Code: %d bytes at 0x%04X.\n Data: %d bytes at 0x%04X.\n" RESET,
codeLength, codeBase, dataLength, dataBase);
if (entry != codeBase) {
printf(GREEN " Starts at 0x%04X.\n" RESET, entry);
}
if (installed > 0) {
printf(GREEN " Vectors: %d. Version 2, so a loader that cannot install them will say so.\n" RESET,
installed);
}
printf(GREEN " Total size: %d bytes.\n" RESET,
SBEX_HEADER_BYTES + codeLength + dataLength + installed * SBEX_VECTOR_ENTRY_BYTES);
}
void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount) {
FILE *outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName);
exit(1);
}
// Write the file header: the magic, the format version, and the features this
// boot image needs from the machine. An emulator that cannot provide one of those
// features refuses the file rather than running it and going quietly wrong.
fwrite(SPLITBIT_MAGIC, sizeof(char), SPLITBIT_MAGIC_LENGTH, outputFile);
fputc(SPLITBIT_FORMAT_VERSION, outputFile);
uint32_t required = SPLITBIT_FEATURES_REQUIRED;
for (int i = SPLITBIT_FLAGS_LENGTH - 1; i >= 0; i--) {
fputc((required >> (i * 8)) & 0xFF, outputFile); // Most significant byte first.
}
// Write the "PRG" header for the program segment
fwrite("PRG", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
// Write the program segment length as a 2-byte value (big-endian)
uint16_t programSize = programCount;
fputc((programSize >> 8) & 0xFF, outputFile); // High byte
fputc(programSize & 0xFF, outputFile); // Low byte
// Write the Program buffer to the file
if (fwrite(Program, sizeof(uint8_t), programCount, outputFile) != (size_t)programCount) {
fprintf(stderr, RED "Error: Failed to write Program data to file \"%s\".\n" RESET, outputFileName);
fclose(outputFile);
exit(1);
}
// Write the "DAT" header for the data segment
fwrite("DAT", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
// Write the data segment length as a 2-byte value (big-endian)
uint16_t dataSize = dataCount;
fputc((dataSize >> 8) & 0xFF, outputFile); // High byte
fputc(dataSize & 0xFF, outputFile); // Low byte
// Write the Data buffer to the file
if (fwrite(Data, sizeof(uint8_t), dataCount, outputFile) != (size_t)dataCount) {
fprintf(stderr, RED "Error: Failed to write Data data to file \"%s\".\n" RESET, outputFileName);
fclose(outputFile);
exit(1);
}
// A program with a base is one meant to be loaded, so it is written out with its
// addresses in front of it and nothing below them. A boot image carries the padding
// because the machine loads it at zero; a loadable one would only be carrying space
// it does not use.
if (programIsLoadable()) {
fclose(outputFile);
writeLoadable(outputFileName, Program, programCount, Data, dataCount);
return;
}
// The Vector Segment, only if the program named any. Leaving it out entirely is
// what lets an image written before vectors existed still load: the reader treats
// the end of the file as an empty table rather than a missing one.
int installed = 0;
for (int i = 0; i < vectorArrayCount; i++) {
if (!vectorArray[i].declaredOnly) {
installed++;
}
}
int vectorBytes = 0;
if (installed > 0) {
fwrite("VEC", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
vectorBytes = installed * VECTOR_ENTRY_FILE_BYTES;
fputc((vectorBytes >> 8) & 0xFF, outputFile);
fputc(vectorBytes & 0xFF, outputFile);
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
uint16_t slot = vectorArray[i].base + (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
fputc((slot >> 8) & 0xFF, outputFile);
fputc(slot & 0xFF, outputFile);
fputc((vectorArray[i].handler >> 8) & 0xFF, outputFile);
fputc(vectorArray[i].handler & 0xFF, outputFile);
}
}
fclose(outputFile);
printf("Successfully wrote SplitBit boot image to \"%s\".\n", outputFileName);
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n" RESET, programCount, dataCount);
if (installed > 0) {
printf(GREEN " Vectors: %d.\n" RESET, installed);
}
printf(GREEN " Total size: %d bytes.\n" RESET,
(programCount + dataCount + SPLITBIT_HEADER_BYTES
+ (installed > 0 ? SEGMENT_MARKER_LENGTH + SEGMENT_LENGTH_BYTES + vectorBytes : 0)));
}