Initial commit
This commit is contained in:
commit
57f209e18c
10 changed files with 3031 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
build-*
|
||||
*.bin
|
||||
*.lst
|
||||
*~
|
||||
*.user
|
16
compiler/compiler.pro
Normal file
16
compiler/compiler.pro
Normal file
|
@ -0,0 +1,16 @@
|
|||
TEMPLATE = app
|
||||
|
||||
include(/home/scott/joey/dist/joey.pri)
|
||||
|
||||
HEADERS += \
|
||||
utlist.h
|
||||
|
||||
SOURCES += \
|
||||
main.c
|
||||
|
||||
OTHER_FILES += \
|
||||
../notes.txt
|
||||
|
||||
DISTFILES += \
|
||||
../test.dbl \
|
||||
../test.lst
|
1088
compiler/main.c
Normal file
1088
compiler/main.c
Normal file
File diff suppressed because it is too large
Load diff
1073
compiler/utlist.h
Normal file
1073
compiler/utlist.h
Normal file
File diff suppressed because it is too large
Load diff
69
notes.txt
Normal file
69
notes.txt
Normal file
|
@ -0,0 +1,69 @@
|
|||
DBLTW ("DoubleThrow") - Don't Build Languages This Way
|
||||
|
||||
Kinda stack based. Stack entries are "variables". A variable is anything.
|
||||
Mainly using a stack based VM to make dealing with variables easier.
|
||||
The compiler is going to be odd. It needs to know about EVERY variable used
|
||||
in EVERY script. Variables are persistent. Setting one in one script makes
|
||||
it available in other scripts. The game engine needs to be able to set and
|
||||
read VM variables somehow. Useful game data such as current map and player
|
||||
position should be in variables for the scripts to use.
|
||||
|
||||
- We say the VM is "kinda stack based" because we skip all the pushing/popping
|
||||
for operations where it's not needed. This may go away.
|
||||
|
||||
- Variables are assigned "memory addresses" (index into an array) by the
|
||||
compiler. This prevents the need to look up where a variable is stored.
|
||||
|
||||
- Exported variables are regular variables that additionally get added to
|
||||
a lookup array so the game engine can find their "memory address" by name.
|
||||
|
||||
- The map compiler needs to generate a script that associates map names with
|
||||
map numbers so authors can use map names in scripts.
|
||||
|
||||
- Stupidly simple pre-processor for constants. It just replaces
|
||||
tokens/variable names with another value.
|
||||
|
||||
- All strings must be in quotes. This allows us to easily determine if
|
||||
text encountered in a script should be treated as a variable name or not.
|
||||
|
||||
- To use a variable in a text string, prefix it with $$.
|
||||
|
||||
- Do we want to load all script data for a map at once? Or individual scripts?
|
||||
|
||||
- The compiler should produce statistics on memory requirements that we can
|
||||
send to the game at startup to configure the VM and not waste resources.
|
||||
|
||||
---
|
||||
|
||||
Preprocessor:
|
||||
|
||||
define <thisThing> <thatThing>
|
||||
include <otherFile>
|
||||
|
||||
Keywords:
|
||||
|
||||
export <variable> <value>
|
||||
set <variable> <value>
|
||||
add <variable> <value>
|
||||
subtract <variable> <value>
|
||||
if <value> [<,>,!=,==] <value> <label>
|
||||
goto <label>
|
||||
call <label>
|
||||
return
|
||||
end
|
||||
do <function> <arg0> <arg1> <...>
|
||||
|
||||
---
|
||||
|
||||
Opcodes:
|
||||
|
||||
add (pulls two from stack, pushes one)
|
||||
puc <constant>
|
||||
puv <variable>
|
||||
pop <variable>
|
||||
jmp (will pull arg from stack)
|
||||
boc (will pull four args from stack)
|
||||
exe (will pull however many args from stack)
|
||||
hlt
|
||||
|
||||
---
|
16
test.dbl
Normal file
16
test.dbl
Normal file
|
@ -0,0 +1,16 @@
|
|||
; Function constants (provided by RPG Engine developer)
|
||||
define print "do 0"
|
||||
|
||||
print "Count is: "
|
||||
export count 5
|
||||
goto loop
|
||||
|
||||
display:
|
||||
print count " "
|
||||
return
|
||||
|
||||
>loop:
|
||||
subtract count 1
|
||||
call display
|
||||
if count > 0 loop
|
||||
end
|
52
vm/main.c
Normal file
52
vm/main.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
|
||||
// /home/scott/joey/rpgengine/dbltw/test.bin
|
||||
|
||||
|
||||
void func_print(VMContextT *context, juint16 args) {
|
||||
int x;
|
||||
for (x=0; x<args; x++) {
|
||||
vmPop(context, &context->working[0]);
|
||||
if (vmIsItemNumber(&context->working[0])) {
|
||||
printf("%d", context->working[0].value);
|
||||
} else {
|
||||
printf("%s", context->working[0].text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
char *programFile;
|
||||
VMContextT context;
|
||||
|
||||
// Get arguments
|
||||
if (argc != 2) {
|
||||
printf("Usage: %s binary.bin\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
programFile = argv[1];
|
||||
|
||||
jlUtilStartup("VM Test");
|
||||
|
||||
if (!vmCreate(&context)) {
|
||||
printf("Unable to initialize VM.\n");
|
||||
} else {
|
||||
vmAddFunction(&context, func_print);
|
||||
if (!vmLoad(&context, programFile)) {
|
||||
printf("Unable to load '%s'.\n", programFile);
|
||||
} else {
|
||||
vmRun(&context);
|
||||
} // vmLoad
|
||||
} // vmCreate
|
||||
|
||||
vmDestroy(&context);
|
||||
|
||||
printf("\nJlStaT = %lu\n", sizeof(jlStaT));
|
||||
|
||||
jlUtilShutdown();
|
||||
}
|
613
vm/vm.c
Normal file
613
vm/vm.c
Normal file
|
@ -0,0 +1,613 @@
|
|||
#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;
|
||||
}
|
82
vm/vm.h
Normal file
82
vm/vm.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#ifndef DBLTW_VM_H
|
||||
#define DBLTW_VM_H
|
||||
|
||||
|
||||
#include "joey.h"
|
||||
|
||||
|
||||
#define OPCODE_ADD 0x01 // Add
|
||||
#define OPCODE_BOC 0x02 // Branch On Condition
|
||||
#define OPCODE_EXE 0x03 // Execute VM Function
|
||||
#define OPCODE_JMP 0x04 // Jump to Address
|
||||
#define OPCODE_HLT 0x05 // Halt Execution
|
||||
#define OPCODE_POP 0x06 // Pop into Variable
|
||||
#define OPCODE_PUC 0x07 // Push Constant
|
||||
#define OPCODE_PUV 0x08 // Push Variable
|
||||
#define OPCODE_SUB 0x09 // Subtract
|
||||
|
||||
#define OPCODE_LAST OPCODE_SUB
|
||||
|
||||
|
||||
typedef struct vmContext_ VMContextT;
|
||||
typedef void (*VMFuncT)(VMContextT *context, juint16 stackCount);
|
||||
|
||||
typedef struct {
|
||||
jint16 value;
|
||||
jint16 allocated;
|
||||
char *text;
|
||||
} PairT; // Signed / string pair.
|
||||
|
||||
typedef struct {
|
||||
juint16 value;
|
||||
jint16 allocated;
|
||||
char *text;
|
||||
} UPairT; // Unsigned / string pair.
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpadded"
|
||||
typedef struct {
|
||||
PairT *stack; // Stack for computations
|
||||
PairT *variables; // All variable storage
|
||||
UPairT *labels; // Lable jump table
|
||||
UPairT *variableTable; // Exported variable name-to-index table
|
||||
juint16 variablesCount; // Number of variables in program
|
||||
juint16 labelCount; // Number of exported labels currently loaded
|
||||
juint16 variableTableCount; // Number of exported variables currently loaded
|
||||
juint16 stackCount; // Maximum size stack has been
|
||||
juint16 sp; // Stack pointer
|
||||
juint16 pc; // Program counter
|
||||
byte *memory; // Program byte stream
|
||||
juint16 programSize; // Number of bytes in program byte stream
|
||||
VMFuncT *functions; // Native function pointers
|
||||
juint16 functionCount; // Number of native functions attached
|
||||
juint16 headerBytes; // Header bytes (used for PC calculations)
|
||||
} PrivateT;
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
typedef struct vmContext_ {
|
||||
PrivateT private;
|
||||
PairT *working; // Working space for operands
|
||||
} VMContextT;
|
||||
|
||||
|
||||
bool vmAddFunction(VMContextT *context, VMFuncT function);
|
||||
bool vmCreate(VMContextT *context);
|
||||
bool vmDestroy(VMContextT *context);
|
||||
bool vmGetPCIndex(VMContextT *context, char *label, juint16 *pc);
|
||||
bool vmGetVariable(VMContextT *context, char *name, PairT *item);
|
||||
bool vmGetVariableIndex(VMContextT *context, char *name, juint16 *index);
|
||||
bool vmIsItemNumber(PairT *item);
|
||||
bool vmLoad(VMContextT *context, char *filename);
|
||||
bool vmPop(VMContextT *context, PairT *item);
|
||||
bool vmPush(VMContextT *context, PairT *item);
|
||||
void vmSetItem(PairT *item, char *text, jint16 *value);
|
||||
bool vmSetPC(VMContextT *context, char *label);
|
||||
bool vmSetPCByIndex(VMContextT *context, juint16 index);
|
||||
bool vmSetVariable(VMContextT *context, char *name, PairT *item);
|
||||
bool vmSetVariableByIndex(VMContextT *context, jint16 index, PairT *item);
|
||||
bool vmStep(VMContextT *context);
|
||||
bool vmRun(VMContextT *context);
|
||||
|
||||
|
||||
#endif
|
17
vm/vm.pro
Normal file
17
vm/vm.pro
Normal file
|
@ -0,0 +1,17 @@
|
|||
TEMPLATE = app
|
||||
|
||||
include(/home/scott/joey/dist/joey.pri)
|
||||
|
||||
HEADERS += \
|
||||
vm.h
|
||||
|
||||
SOURCES += \
|
||||
main.c \
|
||||
vm.c
|
||||
|
||||
OTHER_FILES += \
|
||||
../notes.txt
|
||||
|
||||
DISTFILES += \
|
||||
../test.dbl \
|
||||
../test.lst
|
Loading…
Add table
Reference in a new issue