Long standing assembler bugs fixed, new path system. Make compatibility update.
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include "Assm-util.h"
|
||||
#include "firstPass.h"
|
||||
#include "secondPass.h"
|
||||
@@ -82,11 +84,83 @@ void assemblerCleanup(intermediateElement *intermediateArray, int arraySize, cha
|
||||
free(outputFileName);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
|
||||
void printUsage(const char *programName) {
|
||||
printf("Usage: %s [OPTIONS] <sourcefile>\n", programName);
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -o <file> Write the binary to this path instead of alongside the source.\n");
|
||||
printf(" -I <dir> Look in this directory for included files. May be given more than once.\n");
|
||||
printf(" -M <file> Write the source files this binary depends on, as a make rule.\n");
|
||||
printf(" -h, --help Display this help message.\n");
|
||||
}
|
||||
|
||||
// Writes a make rule naming every source file that went into the binary, so that a
|
||||
// build system knows to reassemble when any of them changes. The empty rules after it
|
||||
// are so that deleting a library does not leave make with a prerequisite it cannot
|
||||
// build; without them the build stops instead of just reassembling.
|
||||
void writeDependencyFile(const char *dependencyPath, const char *outputPath) {
|
||||
FILE *file = fopen(dependencyPath, "w");
|
||||
if (!file) {
|
||||
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, dependencyPath);
|
||||
exit(1);
|
||||
}
|
||||
fprintf(file, "%s:", outputPath);
|
||||
for (int i = 0; i < sourceFileCount(); i++) {
|
||||
fprintf(file, " %s", sourceFile(i));
|
||||
}
|
||||
fprintf(file, "\n\n");
|
||||
// The source itself is index 0, and always exists, so it needs no empty rule.
|
||||
for (int i = 1; i < sourceFileCount(); i++) {
|
||||
fprintf(file, "%s:\n", sourceFile(i));
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
static struct option long_options[] = {
|
||||
{"output", required_argument, 0, 'o'},
|
||||
{"include", required_argument, 0, 'I'},
|
||||
{"depend", required_argument, 0, 'M'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
char *outputFileName = NULL;
|
||||
char *dependencyFileName = NULL;
|
||||
int option_index = 0;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "o:I:M:h", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'o':
|
||||
outputFileName = strdup(optarg);
|
||||
break;
|
||||
case 'I':
|
||||
addIncludeDirectory(optarg);
|
||||
break;
|
||||
case 'M':
|
||||
dependencyFileName = strdup(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
printUsage(argv[0]);
|
||||
return 0;
|
||||
default:
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
fprintf(stderr, RED "Error: No source file specified.\n" RESET);
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
char *fileName = argv[optind];
|
||||
optind++;
|
||||
if (optind < argc) {
|
||||
fprintf(stderr, RED "Error: Unexpected argument: %s\n" RESET, argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Allocate initial space for the intermediate array.
|
||||
size_t arraySize = 1024;
|
||||
intermediateElement *intermediateArray = malloc(arraySize * sizeof(intermediateElement));
|
||||
@@ -95,14 +169,22 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char *fileName = argv[1];
|
||||
// The file named on the command line is the first source file, and the include
|
||||
// list owns the copy that everything else points at.
|
||||
char *source = recordSourceFile(strdup(fileName));
|
||||
int index = 0;
|
||||
loadFile(&intermediateArray, fileName, &index, &arraySize);
|
||||
loadFile(&intermediateArray, source, &index, &arraySize);
|
||||
populateLabelTable(intermediateArray, index);
|
||||
fillInLabelAddresses(intermediateArray, index);
|
||||
populateOutputBuffers(intermediateArray, index, Program, &programLength, Data, &dataLength);
|
||||
char *outputFileName = createOutputFileName(fileName);
|
||||
if (!outputFileName) {
|
||||
outputFileName = createOutputFileName(fileName);
|
||||
}
|
||||
writeOutputFile(outputFileName, Program, programLength, Data, dataLength);
|
||||
if (dependencyFileName) {
|
||||
writeDependencyFile(dependencyFileName, outputFileName);
|
||||
free(dependencyFileName);
|
||||
}
|
||||
assemblerCleanup(intermediateArray, index, outputFileName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -51,13 +51,21 @@ int checkIfInstruction(intermediateElement *currentElement) {
|
||||
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++;
|
||||
// An instruction that works through a Data Pointer names which one by hanging a
|
||||
// selector off the mnemonic, as in LDA.2. LDD and STD move a pointer through a
|
||||
// pointer, so they take two, as in LDD.1.0. Split those off before looking the
|
||||
// mnemonic up.
|
||||
char *selector[MAX_DATA_POINTER_OPERANDS] = { NULL };
|
||||
int selectorsGiven = 0;
|
||||
char *dot = strchr(token, '.');
|
||||
while (dot) {
|
||||
*dot = '\0';
|
||||
dot++;
|
||||
if (selectorsGiven < MAX_DATA_POINTER_OPERANDS) {
|
||||
selector[selectorsGiven] = dot;
|
||||
}
|
||||
selectorsGiven++;
|
||||
dot = strchr(dot, '.');
|
||||
}
|
||||
|
||||
uint8_t opcode = getOpcode(token);
|
||||
@@ -66,32 +74,38 @@ int checkIfInstruction(intermediateElement *currentElement) {
|
||||
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;
|
||||
int selectorsWanted = dataPointerOperands(opcode);
|
||||
if (selectorsGiven > selectorsWanted) {
|
||||
if (selectorsWanted == 0) {
|
||||
fprintf(stderr, RED "Error: %s does not work through a Data Pointer, so it cannot take a selector.\n" RESET, token);
|
||||
} else {
|
||||
fprintf(stderr, RED "Error: %s takes %d Data Pointer selector(s), but \"%s\" gives %d.\n" RESET, token, selectorsWanted, currentElement->token, selectorsGiven);
|
||||
}
|
||||
} 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);
|
||||
currentElement->type = INSTRUCTION;
|
||||
currentElement->byteValue = opcode;
|
||||
// The selectors are emitted whether or not they were written, so the length is
|
||||
// fixed by the instruction. Leaving one off is the same as writing 0.
|
||||
currentElement->byteLength = 1 + selectorsWanted;
|
||||
|
||||
for (int i = 0; i < selectorsWanted; i++) {
|
||||
currentElement->dataPointer[i] = 0;
|
||||
if (i < selectorsGiven && selector[i]) {
|
||||
char *end;
|
||||
long value = strtol(selector[i], &end, 10);
|
||||
if (*selector[i] == '\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[i] = (uint8_t)value;
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) printf("Token: %s is an instruction with %d selector(s).\n", currentElement->token, selectorsWanted);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
|
||||
#define MAX_INCLUDES 128
|
||||
|
||||
@@ -48,7 +49,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.
|
||||
uint8_t dataPointer[MAX_DATA_POINTER_OPERANDS]; // Which Data Pointers this instruction works through, if any.
|
||||
int byteLength;
|
||||
uint16_t address;
|
||||
int type; // "KEYWORD", "INSTRUCTION" , "LABEL" , "VALUE", "STRING"
|
||||
|
||||
@@ -59,6 +59,8 @@ Instruction instruction_set[] = {
|
||||
{0x47, "SETD"},
|
||||
{0x48, "DPUP"},
|
||||
{0x49, "DPDN"},
|
||||
{0x4A, "LDD"},
|
||||
{0x4B, "STD"},
|
||||
// Output Operations:
|
||||
{0xD0, "OUTQ"},
|
||||
{0xD1, "OUTA"},
|
||||
@@ -82,10 +84,14 @@ 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.
|
||||
int dataPointerOperands(uint8_t opcode) {
|
||||
// How many Data Pointer selector bytes follow this opcode. Most instructions
|
||||
// have none. The ones that work through a pointer have one naming which pointer.
|
||||
// LDD and STD move a pointer through a pointer, so they name two.
|
||||
switch (opcode) {
|
||||
case 0x4A: // LDD
|
||||
case 0x4B: // STD
|
||||
return 2;
|
||||
case 0x33: // PSHD
|
||||
case 0x36: // POPD
|
||||
case 0x40: // INCD
|
||||
|
||||
@@ -8,10 +8,52 @@
|
||||
#ifndef ASSEMBLY_H
|
||||
#define ASSEMBLY_H
|
||||
|
||||
// ---- The SplitBit binary format ----
|
||||
//
|
||||
// A binary starts with a file header, then the Program Segment, then the Data
|
||||
// Segment. All multi byte numbers are stored most significant byte first.
|
||||
//
|
||||
// Offset Size Field
|
||||
// 0 4 "SPBT", so a file that is not a SplitBit binary is spotted at once
|
||||
// 4 1 Format version
|
||||
// 5 4 Required feature flags
|
||||
// 9 3 "PRG"
|
||||
// 12 2 Program Segment length
|
||||
// 14 N Program Segment
|
||||
// .. 3 "DAT"
|
||||
// .. 2 Data Segment length
|
||||
// .. M Data Segment
|
||||
//
|
||||
// The feature flags are how a binary says it needs something the base machine does
|
||||
// not provide, so that an emulator which cannot provide it refuses to run the binary
|
||||
// rather than quietly doing the wrong thing. No features are defined yet; the field
|
||||
// is here so that adding one later does not need another format version.
|
||||
|
||||
#define SPLITBIT_MAGIC "SPBT"
|
||||
#define SPLITBIT_MAGIC_LENGTH 4
|
||||
#define SPLITBIT_FORMAT_VERSION 1
|
||||
#define SPLITBIT_FLAGS_LENGTH 4
|
||||
#define SEGMENT_MARKER_LENGTH 3
|
||||
#define SEGMENT_LENGTH_BYTES 2
|
||||
|
||||
// Everything the format costs a file, on top of the two segments themselves.
|
||||
#define SPLITBIT_HEADER_BYTES (SPLITBIT_MAGIC_LENGTH + 1 + SPLITBIT_FLAGS_LENGTH \
|
||||
+ 2 * (SEGMENT_MARKER_LENGTH + SEGMENT_LENGTH_BYTES))
|
||||
|
||||
// Features this build of the emulator can provide. A binary asking for anything
|
||||
// outside this set is refused.
|
||||
#define SPLITBIT_FEATURES_SUPPORTED 0x00000000u
|
||||
|
||||
// Features the assembler currently needs to ask for. Nothing, so far.
|
||||
#define SPLITBIT_FEATURES_REQUIRED 0x00000000u
|
||||
|
||||
const char* getMnemonic(uint8_t opcode);
|
||||
|
||||
uint8_t getOpcode(char* mnemonic);
|
||||
|
||||
int instructionTakesDataPointer(uint8_t opcode);
|
||||
// How many Data Pointer selector bytes follow the given opcode. Never more than two.
|
||||
#define MAX_DATA_POINTER_OPERANDS 2
|
||||
|
||||
int dataPointerOperands(uint8_t opcode);
|
||||
|
||||
#endif // CPU_H
|
||||
|
||||
+138
-24
@@ -7,46 +7,153 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include "firstPass.h"
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
|
||||
char *includeList[MAX_INCLUDES];
|
||||
// Each source file that has gone into this assembly. The path is kept as it was
|
||||
// written, because that is what reads well in an error message or a dependency
|
||||
// rule. The canonical form is kept alongside it so that the same file reached by
|
||||
// two different routes is recognised as one file and included only once.
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *canonical;
|
||||
} SourceFile;
|
||||
|
||||
SourceFile includeList[MAX_INCLUDES];
|
||||
int includeCount = 0;
|
||||
|
||||
char *includeDirectories[MAX_INCLUDES];
|
||||
int includeDirectoryCount = 0;
|
||||
|
||||
void freeIncludeList() {
|
||||
for (int i = 0; i < includeCount; i++) {
|
||||
if (includeList[i]) {
|
||||
free(includeList[i]);
|
||||
}
|
||||
free(includeList[i].path);
|
||||
free(includeList[i].canonical);
|
||||
}
|
||||
includeCount = 0;
|
||||
for (int i = 0; i < includeDirectoryCount; i++) {
|
||||
free(includeDirectories[i]);
|
||||
}
|
||||
includeDirectoryCount = 0;
|
||||
}
|
||||
|
||||
int isFileIncluded(const char *fileName) {
|
||||
void addIncludeDirectory(const char *directory) {
|
||||
if (includeDirectoryCount >= MAX_INCLUDES) {
|
||||
fprintf(stderr, RED "Error: Too many include directories.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
includeDirectories[includeDirectoryCount] = strdup(directory);
|
||||
includeDirectoryCount++;
|
||||
}
|
||||
|
||||
int sourceFileCount() {
|
||||
return includeCount;
|
||||
}
|
||||
|
||||
const char *sourceFile(int index) {
|
||||
return includeList[index].path;
|
||||
}
|
||||
|
||||
char *recordSourceFile(char *path) {
|
||||
// realpath resolves symlinks and things like "..", so that Libraries/print.asm
|
||||
// and ../Libraries/print.asm are known to be the same file.
|
||||
char *canonical = realpath(path, NULL);
|
||||
if (!canonical) {
|
||||
canonical = strdup(path);
|
||||
}
|
||||
for (int i = 0; i < includeCount; i++) {
|
||||
if (strcmp(includeList[i], fileName) == 0) {
|
||||
return 1;
|
||||
if (strcmp(includeList[i].canonical, canonical) == 0) {
|
||||
// Already assembled once. Including it again is not an error, it just
|
||||
// does nothing, which is what lets two libraries share a dependency.
|
||||
free(canonical);
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (includeCount >= MAX_INCLUDES) {
|
||||
fprintf(stderr, RED "Error: Too many included files.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
includeList[includeCount].path = path;
|
||||
includeList[includeCount].canonical = canonical;
|
||||
includeCount++;
|
||||
return path;
|
||||
}
|
||||
|
||||
// Joins a directory and a file name into a newly allocated path.
|
||||
static char *joinPath(const char *directory, const char *name) {
|
||||
if (!directory || directory[0] == '\0' || strcmp(directory, ".") == 0) {
|
||||
return strdup(name);
|
||||
}
|
||||
size_t length = strlen(directory) + 1 + strlen(name) + 1;
|
||||
char *joined = malloc(length);
|
||||
if (!joined) {
|
||||
fprintf(stderr, RED "Error: Memory allocation failed.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
snprintf(joined, length, "%s/%s", directory, name);
|
||||
return joined;
|
||||
}
|
||||
|
||||
static int fileExists(const char *path) {
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void addIncludedFile(const char *fileName) {
|
||||
if (includeCount < MAX_INCLUDES) {
|
||||
includeList[includeCount] = strdup(fileName);
|
||||
includeCount++;
|
||||
} else {
|
||||
fprintf(stderr, RED "Error: Too many included files.\n" RESET);
|
||||
exit(1);
|
||||
// Works out where an #Include actually points. An absolute path is taken as given.
|
||||
// Otherwise the file is looked for beside the file that asked for it, so that a
|
||||
// library including its own siblings needs no help, and then along the include
|
||||
// directories in the order they were given. Returns NULL if it is nowhere.
|
||||
static char *resolveInclude(const char *includingFile, const char *requested) {
|
||||
if (requested[0] == '/') {
|
||||
return fileExists(requested) ? strdup(requested) : NULL;
|
||||
}
|
||||
|
||||
char *copy = strdup(includingFile);
|
||||
char *candidate = joinPath(dirname(copy), requested);
|
||||
free(copy);
|
||||
if (fileExists(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
free(candidate);
|
||||
|
||||
for (int i = 0; i < includeDirectoryCount; i++) {
|
||||
candidate = joinPath(includeDirectories[i], requested);
|
||||
if (fileExists(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
free(candidate);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Says where the assembler looked, so that a missing include is easy to fix.
|
||||
static void reportMissingInclude(const char *includingFile, const char *requested, int lineNumber) {
|
||||
fprintf(stderr, RED "Error: Couldn't find included file \"%s\".\n" RESET, requested);
|
||||
printf(" File: %s at line %d.\n", includingFile, lineNumber);
|
||||
printf(" Looked for it:\n");
|
||||
char *copy = strdup(includingFile);
|
||||
printf(" beside %s\n", includingFile);
|
||||
free(copy);
|
||||
for (int i = 0; i < includeDirectoryCount; i++) {
|
||||
printf(" in %s\n", includeDirectories[i]);
|
||||
}
|
||||
if (includeDirectoryCount == 0) {
|
||||
printf(" and nowhere else, because no include directories were given with -I.\n");
|
||||
}
|
||||
}
|
||||
|
||||
int loadFile(intermediateElement **intermediateArray, char *fileName, int *intermediateIndex, size_t *arraySize){
|
||||
// Initial setup.
|
||||
addIncludedFile(fileName);
|
||||
// fileName is a path already resolved and recorded by the caller, and the copy
|
||||
// the include list owns, so element fileNames can safely point at it.
|
||||
int status = NOWHERE;
|
||||
int lineNumber = 1; // Line numbers start at 1.
|
||||
// Open the file.
|
||||
@@ -73,19 +180,26 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
||||
int testValue = checkIfKeyword(&(*intermediateArray)[*intermediateIndex]);
|
||||
if(testValue > 0) {
|
||||
switch (testValue){
|
||||
case KEYWORD_INCLUDE:
|
||||
case KEYWORD_INCLUDE: {
|
||||
// We need to load another file and process it before continuing.
|
||||
status = NOWHERE;
|
||||
// Get the filename and load up the file.
|
||||
// Get the filename and work out where it actually is.
|
||||
(*intermediateIndex)++;
|
||||
readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber);
|
||||
char *includeFile = (*intermediateArray)[*intermediateIndex].token;
|
||||
if (isFileIncluded(includeFile)) {
|
||||
fprintf(stderr, RED "Error: File %s is included more than once.\n" RESET, includeFile);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
char *requested = (*intermediateArray)[*intermediateIndex].token;
|
||||
char *resolved = resolveInclude(fileName, requested);
|
||||
if (!resolved) {
|
||||
reportMissingInclude(fileName, requested, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
loadFile(intermediateArray, includeFile, intermediateIndex, arraySize);
|
||||
// recordSourceFile takes the path, and hands back NULL if this file
|
||||
// has already been assembled. Including it twice is harmless, which
|
||||
// is what lets two libraries depend on a third.
|
||||
char *owned = recordSourceFile(resolved);
|
||||
if (owned) {
|
||||
loadFile(intermediateArray, owned, intermediateIndex, arraySize);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEYWORD_PROGRAM:
|
||||
// Set the state PROGRAM so we mark additional tokens for inclusion into Program Memory.
|
||||
|
||||
@@ -14,6 +14,18 @@
|
||||
|
||||
void freeIncludeList();
|
||||
|
||||
// Adds a directory to search when an #Include is not found beside the file that
|
||||
// asked for it. Directories are searched in the order they were added.
|
||||
void addIncludeDirectory(const char *directory);
|
||||
|
||||
// Records a source file and returns the copy the assembler will keep, which is what
|
||||
// element fileNames point at. Returns NULL if this file has already been included.
|
||||
char *recordSourceFile(char *path);
|
||||
|
||||
// Every source file that went into this assembly, for writing make dependencies.
|
||||
int sourceFileCount();
|
||||
const char *sourceFile(int index);
|
||||
|
||||
int loadFile(intermediateElement **intermediateArray, char *fileName, int *intermediateIndex, size_t *arraySize);
|
||||
|
||||
#endif // FIRSTPASS_H
|
||||
|
||||
@@ -162,8 +162,8 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
|
||||
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.
|
||||
if (instructionTakesDataPointer(intermediateArray[i].byteValue)) {
|
||||
Program[(*programCount)++] = intermediateArray[i].dataPointer;
|
||||
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);
|
||||
@@ -192,12 +192,12 @@ 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);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -211,8 +211,18 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Write the file header: the magic, the format version, and the features this
|
||||
// binary 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), 3, outputFile);
|
||||
fwrite("PRG", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
|
||||
|
||||
// Write the program segment length as a 2-byte value (big-endian)
|
||||
uint16_t programSize = programCount;
|
||||
@@ -227,7 +237,7 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
|
||||
}
|
||||
|
||||
// Write the "DAT" header for the data segment
|
||||
fwrite("DAT", sizeof(char), 3, outputFile);
|
||||
fwrite("DAT", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
|
||||
|
||||
// Write the data segment length as a 2-byte value (big-endian)
|
||||
uint16_t dataSize = dataCount;
|
||||
@@ -243,5 +253,5 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
|
||||
|
||||
fclose(outputFile);
|
||||
printf("Successfully wrote SplitBit binary to \"%s\".\n", outputFileName);
|
||||
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n Total size: %d bytes.\n" RESET, programCount, dataCount, (programCount+dataCount+10));
|
||||
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n Total size: %d bytes.\n" RESET, programCount, dataCount, (programCount + dataCount + SPLITBIT_HEADER_BYTES));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user