613 lines
16 KiB
C
613 lines
16 KiB
C
#include "vm.h"
|
|
#include "stdio.h"
|
|
#include "stdarg.h"
|
|
#include "string.h"
|
|
|
|
|
|
// Number of scratch items for opcodes to use as working space.
|
|
// Depends on VM implementation so it's declared here.
|
|
#define WORKING_SIZE 4
|
|
|
|
|
|
typedef void (*VMOpCodeT)(VMContextT *context);
|
|
|
|
|
|
void copyItem(PairT *item, PairT *source);
|
|
void op_add(VMContextT *context);
|
|
void op_boc(VMContextT *context);
|
|
void op_exe(VMContextT *context);
|
|
void op_jmp(VMContextT *context);
|
|
void op_hlt(VMContextT *context);
|
|
void op_pop(VMContextT *context);
|
|
void op_puc(VMContextT *context);
|
|
void op_puv(VMContextT *context);
|
|
void op_sub(VMContextT *context);
|
|
void readItemFromPC(VMContextT *context, PairT *item);
|
|
void setStringFormat(PairT *item, char *format, ...);
|
|
|
|
|
|
static VMOpCodeT opcodes[OPCODE_LAST];
|
|
|
|
|
|
void copyItem(PairT *item, PairT *source) {
|
|
vmSetItem(item, source->text, &source->value);
|
|
}
|
|
|
|
|
|
void op_add(VMContextT *context) {
|
|
bool isString0;
|
|
bool isString1;
|
|
|
|
vmPop(context, &context->working[0]);
|
|
vmPop(context, &context->working[1]);
|
|
isString0 = !vmIsItemNumber(&context->working[0]);
|
|
isString1 = !vmIsItemNumber(&context->working[1]);
|
|
|
|
// Both operands need to be numbers to result in a number.
|
|
if (isString0 || isString1) {
|
|
// Are both strings?
|
|
if (isString0 && isString1) {
|
|
setStringFormat(&context->working[2], "%s%s", context->working[0].text, context->working[1].text);
|
|
} else {
|
|
// Is the first a string?
|
|
if (isString0) {
|
|
setStringFormat(&context->working[2], "%s%d", context->working[0].text, context->working[1].value);
|
|
} else {
|
|
setStringFormat(&context->working[2], "%d%s", context->working[0].value, context->working[1].text);
|
|
}
|
|
}
|
|
context->working[2].value = (jint16)strlen(context->working[2].text);
|
|
} else {
|
|
// Number
|
|
context->working[2].value = context->working[0].value + context->working[1].value;
|
|
}
|
|
|
|
vmPush(context, &context->working[2]);
|
|
}
|
|
|
|
|
|
void op_boc(VMContextT *context) {
|
|
bool isNumber0;
|
|
bool isNumber2;
|
|
int result;
|
|
|
|
vmPop(context, &context->working[0]); // arg1
|
|
vmPop(context, &context->working[1]); // compairson
|
|
vmPop(context, &context->working[2]); // arg2
|
|
vmPop(context, &context->working[3]); // target pc
|
|
isNumber0 = vmIsItemNumber(&context->working[0]);
|
|
isNumber2 = vmIsItemNumber(&context->working[2]);
|
|
|
|
// Both Numbers?
|
|
if (isNumber0 && isNumber2) {
|
|
switch (context->working[1].value) {
|
|
case 0: // =
|
|
if (context->working[0].value == context->working[2].value) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 1: // !
|
|
if (context->working[0].value != context->working[2].value) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 2: // <
|
|
if (context->working[0].value < context->working[2].value) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 3: // >
|
|
if (context->working[0].value > context->working[2].value) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// Which side is a number?
|
|
if (isNumber0) {
|
|
setStringFormat(&context->working[2], "%d", context->working[2].value);
|
|
} else {
|
|
setStringFormat(&context->working[0], "%d", context->working[0].value);
|
|
}
|
|
result = strcmp(context->working[0].text, context->working[2].text);
|
|
switch (context->working[1].value) {
|
|
case 0: // =
|
|
if (result == 0) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 1: // !
|
|
if (result != 0) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 2: // <
|
|
if (result < 0) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
|
|
case 3: // >
|
|
if (result > 0) {
|
|
context->private.pc = (juint16)context->working[3].value;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void op_exe(VMContextT *context) {
|
|
vmPop(context, &context->working[0]); // Arg count
|
|
vmPop(context, &context->working[1]); // Function number
|
|
context->private.functions[context->working[1].value](context, (juint16)(context->working[0].value) - 1);
|
|
}
|
|
|
|
|
|
void op_jmp(VMContextT *context) {
|
|
vmPop(context, &context->working[0]);
|
|
//printf("Jumping to %04x\n", (juint16)context->working[0].value);
|
|
context->private.pc = (juint16)context->working[0].value;
|
|
}
|
|
|
|
|
|
void op_hlt(VMContextT *context) {
|
|
(void)(context);
|
|
// We don't actually need to do anything for HLT.
|
|
}
|
|
|
|
|
|
void op_pop(VMContextT *context) {
|
|
readItemFromPC(context, &context->working[0]);
|
|
vmPop(context, &context->working[1]);
|
|
copyItem(&context->private.variables[context->working[0].value], &context->working[1]);
|
|
}
|
|
|
|
|
|
void op_puc(VMContextT *context) {
|
|
readItemFromPC(context, &context->working[0]);
|
|
vmPush(context, &context->working[0]);
|
|
}
|
|
|
|
|
|
void op_puv(VMContextT *context) {
|
|
readItemFromPC(context, &context->working[0]);
|
|
vmPush(context, &context->private.variables[context->working[0].value]);
|
|
}
|
|
|
|
|
|
void op_sub(VMContextT *context) {
|
|
vmPop(context, &context->working[0]);
|
|
vmPop(context, &context->working[1]);
|
|
|
|
// Number
|
|
context->working[2].value = context->working[0].value - context->working[1].value;
|
|
|
|
vmPush(context, &context->working[2]);
|
|
}
|
|
|
|
|
|
void readItemFromPC(VMContextT *context, PairT *item) {
|
|
byte b1;
|
|
byte b2;
|
|
int y;
|
|
|
|
//printf("Reading item at offset %04x - %02x\n", context->private.pc + context->private.headerBytes, context->private.memory[context->private.pc]);
|
|
|
|
// Get the length/value of this item
|
|
b1 = context->private.memory[context->private.pc++];
|
|
b2 = context->private.memory[context->private.pc++];
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
item->value = (jint16)((b1 << 8) + b2);
|
|
#else
|
|
item->value = (jint16)((b2 << 8) + b1);
|
|
#endif
|
|
|
|
// If this is a zero, it's a number. If it's not, it's a string
|
|
if (context->private.memory[context->private.pc] > 0) {
|
|
if (item->allocated < item->value + 1) {
|
|
item->text = (char *)jlRealloc(item->text, (size_t)(item->value + 1));
|
|
item->allocated = item->value + 1;
|
|
}
|
|
for (y=0; y<item->value; y++) {
|
|
item->text[y] = (char)context->private.memory[context->private.pc++];
|
|
}
|
|
item->text[y] = 0;
|
|
} else {
|
|
context->private.pc++;
|
|
item->text[0] = 0;
|
|
}
|
|
|
|
//printf("Ending read (inclusive) at offset %04x - %02x\n", context->private.pc + context->private.headerBytes - 1, context->private.memory[context->private.pc - 1]);
|
|
}
|
|
|
|
|
|
void vmSetItem(PairT *item, char *text, jint16 *value) {
|
|
juint16 len;
|
|
|
|
if ((text != 0) && (text[0] != 0)) {
|
|
len = (juint16)strlen(text) + 1;
|
|
if (item->allocated < len) {
|
|
item->text = (char *)jlRealloc(item->text, len);
|
|
item->allocated = (jint16)len;
|
|
}
|
|
strcpy(item->text, text);
|
|
item->value = (jint16)len - 1;
|
|
} else {
|
|
if (item->text != 0) {
|
|
item->text[0] = 0;
|
|
}
|
|
}
|
|
|
|
if (value != 0) {
|
|
item->value = *value;
|
|
}
|
|
}
|
|
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
void setStringFormat(PairT *item, char *format, ...) {
|
|
int len;
|
|
va_list args;
|
|
|
|
// Do we have a buffer at all?
|
|
if (item->allocated == 0) {
|
|
item->text = (char *)jlMalloc(16 * sizeof(char));
|
|
item->allocated = 16;
|
|
}
|
|
// Attempt to write formatted string into buffer.
|
|
va_start(args, format);
|
|
len = vsnprintf(item->text, (unsigned long)item->allocated, format, args);
|
|
va_end(args);
|
|
// Did it succeed?
|
|
if (len >= item->allocated) {
|
|
// Nope! Allocate more memory and do it again.
|
|
item->text = (char *)jlRealloc(item->text, (unsigned long)len);
|
|
item->allocated = (jint16)len;
|
|
va_start(args, format);
|
|
len = vsnprintf(item->text, (unsigned long)item->allocated, format, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
|
bool vmAddFunction(VMContextT *context, VMFuncT function) {
|
|
context->private.functionCount++;
|
|
context->private.functions = (VMFuncT *)jlRealloc(context->private.functions, sizeof(VMFuncT) * context->private.functionCount);
|
|
context->private.functions[context->private.functionCount - 1] = function;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmCreate(VMContextT *context) {
|
|
juint16 x;
|
|
|
|
// This can be redundant but it doesn't hurt anything.
|
|
opcodes[OPCODE_ADD - 1] = op_add;
|
|
opcodes[OPCODE_BOC - 1] = op_boc;
|
|
opcodes[OPCODE_EXE - 1] = op_exe;
|
|
opcodes[OPCODE_JMP - 1] = op_jmp;
|
|
opcodes[OPCODE_HLT - 1] = op_hlt;
|
|
opcodes[OPCODE_POP - 1] = op_pop;
|
|
opcodes[OPCODE_PUC - 1] = op_puc;
|
|
opcodes[OPCODE_PUV - 1] = op_puv;
|
|
opcodes[OPCODE_SUB - 1] = op_sub;
|
|
|
|
context->private.sp = 0;
|
|
context->private.pc = 0;
|
|
context->private.headerBytes = 0;
|
|
|
|
// Stack
|
|
context->private.stack = 0;
|
|
context->private.stackCount = 0;
|
|
|
|
// All variables
|
|
context->private.variables = 0;
|
|
context->private.variablesCount = 0;
|
|
|
|
// Native function pointers
|
|
context->private.functions = 0;
|
|
context->private.functionCount = 0;
|
|
|
|
// Program space
|
|
context->private.memory = 0;
|
|
context->private.programSize = 0;
|
|
|
|
// Exported Labels
|
|
context->private.labels = 0;
|
|
context->private.labelCount = 0;
|
|
|
|
// Exported Variables
|
|
context->private.variableTable = 0;
|
|
context->private.variableTableCount = 0;
|
|
|
|
// Working RAM
|
|
context->working = (PairT *)jlMalloc(sizeof(PairT) * WORKING_SIZE);
|
|
for (x=0; x<WORKING_SIZE; x++) {
|
|
context->working[x].text = 0;
|
|
context->working[x].allocated = 0;
|
|
context->working[x].value = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmDestroy(VMContextT *context) {
|
|
|
|
juint16 x;
|
|
|
|
// Working RAM
|
|
for (x=0; x<WORKING_SIZE; x++) {
|
|
jlFree(context->working[x].text);
|
|
}
|
|
jlFree(context->working);
|
|
|
|
// Program space
|
|
jlFree(context->private.memory);
|
|
|
|
// Native function pointers
|
|
jlFree(context->private.functions);
|
|
|
|
// Label Jump Table
|
|
for (x=0; x<context->private.labelCount; x++) {
|
|
jlFree(context->private.labels[x].text);
|
|
}
|
|
jlFree(context->private.labels);
|
|
|
|
// Exported Varaibles Index
|
|
for (x=0; x<context->private.variableTableCount; x++) {
|
|
jlFree(context->private.variableTable[x].text);
|
|
}
|
|
jlFree(context->private.variableTable);
|
|
|
|
// All variables
|
|
for (x=0; x<context->private.variablesCount; x++) {
|
|
jlFree(context->private.variables[x].text);
|
|
}
|
|
jlFree(context->private.variables);
|
|
|
|
// Stack
|
|
for (x=0; x<context->private.stackCount; x++) {
|
|
jlFree(context->private.stack[x].text);
|
|
}
|
|
jlFree(context->private.stack);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmGetPCIndex(VMContextT *context, char *label, juint16 *pc) {
|
|
juint16 x;
|
|
bool result = false;
|
|
|
|
for (x=0; x<context->private.labelCount; x++) {
|
|
if (strcmp(context->private.labels[x].text, label) == 0) {
|
|
*pc = context->private.labels[x].value;
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool vmGetVariable(VMContextT *context, char *name, PairT *item) {
|
|
juint16 x;
|
|
|
|
item = NULL;
|
|
|
|
if (vmGetVariableIndex(context, name, &x)) {
|
|
*item = context->private.variables[x];
|
|
}
|
|
|
|
return (item != NULL);
|
|
}
|
|
|
|
|
|
bool vmGetVariableIndex(VMContextT *context, char *name, juint16 *index) {
|
|
juint16 x;
|
|
|
|
for (x=0; x<context->private.variableTableCount; x++) {
|
|
if (strcmp(context->private.variableTable[x].text, name) == 0) {
|
|
*index = context->private.variableTable[x].value;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool vmIsItemNumber(PairT *item) {
|
|
return (item->text == 0) || (item->text[0] == 0);
|
|
}
|
|
|
|
|
|
bool vmLoad(VMContextT *context, char *filename) {
|
|
|
|
char temp[4];
|
|
FILE *binFile;
|
|
juint16 tempUnsigned;
|
|
juint16 x;
|
|
juint16 y;
|
|
int length;
|
|
|
|
binFile = fopen(filename, "rb");
|
|
if (!binFile) {
|
|
return false;
|
|
}
|
|
|
|
// Get file size for later
|
|
fseek(binFile, 0, SEEK_END);
|
|
length = (int)ftell(binFile);
|
|
fseek(binFile, 0, SEEK_SET);
|
|
|
|
// Read header & Version Number
|
|
fread(temp, 4, 1, binFile);
|
|
if ((temp[0] != 'D') || (temp[1] != 'B') || (temp[2] != 'L') || (temp[3] != 0)) {
|
|
fclose(binFile);
|
|
return false;
|
|
}
|
|
|
|
// Read exported label entries
|
|
fread(&tempUnsigned, sizeof(juint16), 1, binFile);
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
context->private.labelCount = jlByteSwap(tempUnsigned);
|
|
#else
|
|
context->private.labelCount = tempUnsigned;
|
|
#endif
|
|
context->private.labels = (UPairT *)jlMalloc(sizeof(UPairT) * context->private.labelCount);
|
|
for (x=0; x<context->private.labelCount; x++) {
|
|
fread(&tempUnsigned, sizeof(juint16), 1, binFile); // Address of label
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
context->private.labels[x].value = jlByteSwap(tempUnsigned);
|
|
#else
|
|
context->private.labels[x].value = tempUnsigned;
|
|
#endif
|
|
y = (juint16)fgetc(binFile); // Length of label name
|
|
// Label Name
|
|
context->private.labels[x].text = (char *)jlMalloc((y + 1) * sizeof(char));
|
|
fread(context->private.labels[x].text, sizeof(char), y, binFile);
|
|
context->private.labels[x].text[y] = 0;
|
|
context->private.labels[x].allocated = (jint16)(y + 1);
|
|
}
|
|
|
|
// Read exported variable entries
|
|
fread(&tempUnsigned, sizeof(juint16), 1, binFile);
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
context->private.variableTableCount = jlByteSwap(tempUnsigned);
|
|
#else
|
|
context->private.variableTableCount = tempUnsigned;
|
|
#endif
|
|
context->private.variableTable = (UPairT *)jlMalloc(sizeof(UPairT) * context->private.variableTableCount);
|
|
for (x=0; x<context->private.variableTableCount; x++) {
|
|
fread(&tempUnsigned, sizeof(juint16), 1, binFile);
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
context->private.variableTable[x].value = jlByteSwap(tempUnsigned);
|
|
#else
|
|
context->private.variableTable[x].value = tempUnsigned;
|
|
#endif
|
|
y = (juint16)fgetc(binFile); // Length of variable name
|
|
// Variable Name
|
|
context->private.variableTable[x].text = (char *)jlMalloc((y + 1) * sizeof(char));
|
|
fread(context->private.variableTable[x].text, sizeof(char), y, binFile);
|
|
context->private.variableTable[x].text[y] = 0;
|
|
context->private.variableTable[x].allocated = (jint16)(y + 1);
|
|
}
|
|
|
|
// Read total variable count
|
|
fread(&tempUnsigned, sizeof(juint16), 1, binFile);
|
|
#ifdef JOEY_BIG_ENDIAN
|
|
context->private.variablesCount = jlByteSwap(tempUnsigned);
|
|
#else
|
|
context->private.variablesCount = tempUnsigned;
|
|
#endif
|
|
context->private.variables = (PairT *)jlMalloc(sizeof(PairT) * context->private.variablesCount);
|
|
for (x=0; x<context->private.variablesCount; x++) {
|
|
context->private.variables->text = 0;
|
|
context->private.variables->allocated = 0;
|
|
context->private.variables->value = 0;
|
|
}
|
|
|
|
// Record position in file for debugging purposes
|
|
context->private.headerBytes = (juint16)ftell(binFile);
|
|
//printf("PC offset is %04x\n", context->private.headerBytes);
|
|
|
|
// Read program byte stream
|
|
context->private.programSize = (juint16)(length - ftell(binFile));
|
|
context->private.memory = (byte *)jlMalloc(sizeof(byte) * context->private.programSize);
|
|
fread(context->private.memory, sizeof(char) * context->private.programSize, 1, binFile);
|
|
|
|
fclose(binFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmPop(VMContextT *context, PairT *item) {
|
|
if (context->private.sp > 0) {
|
|
context->private.sp--;
|
|
copyItem(item, &context->private.stack[context->private.sp]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool vmPush(VMContextT *context, PairT *item) {
|
|
// Grow stack if needed.
|
|
if (context->private.sp + 1 > context->private.stackCount) {
|
|
context->private.stackCount = context->private.sp + 1;
|
|
context->private.stack = (PairT *)jlRealloc(context->private.stack, sizeof(PairT) * context->private.stackCount);
|
|
context->private.stack[context->private.sp].text = 0;
|
|
context->private.stack[context->private.sp].allocated = 0;
|
|
context->private.stack[context->private.sp].value = 0;
|
|
}
|
|
copyItem(&context->private.stack[context->private.sp], item);
|
|
context->private.sp++;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmSetPC(VMContextT *context, char *label) {
|
|
juint16 pc;
|
|
bool result = false;
|
|
|
|
if (label == NULL) {
|
|
context->private.pc = 0;
|
|
result = true;
|
|
} else {
|
|
if (vmGetPCIndex(context, label, &pc)) {
|
|
context->private.pc = pc;
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool vmSetPCByIndex(VMContextT *context, juint16 index) {
|
|
context->private.pc = index; // Currently, the PC is the index we return
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmSetVariable(VMContextT *context, char *name, PairT *item) {
|
|
PairT *target = NULL;
|
|
if (vmGetVariable(context, name, target)) {
|
|
copyItem(target, item);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool vmSetVariableByIndex(VMContextT *context, jint16 index, PairT *item) {
|
|
copyItem(&context->private.variables[index], item);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool vmStep(VMContextT *context) {
|
|
byte opcode = context->private.memory[context->private.pc++];
|
|
//printf("Fetched opcode at file offset %04x (PC %04x): %02x\n", context->private.pc + context->private.headerBytes - 1, context->private.pc - 1, opcode);
|
|
opcodes[opcode - 1](context);
|
|
return (opcode != OPCODE_HLT);
|
|
}
|
|
|
|
|
|
bool vmRun(VMContextT *context) {
|
|
while (vmStep(context))
|
|
;
|
|
return true;
|
|
}
|