diff --git a/include/ground.h b/include/ground.h index 4863539..840bd81 100644 --- a/include/ground.h +++ b/include/ground.h @@ -425,6 +425,8 @@ struct _Ground { } Log; struct { + void (*save) (GroundBytecode* bytecode, const char* path); + GroundBytecode (*load) (const char* path); struct { void (*execute) (GroundBytecodeProgram* program, GroundBytecodeHeap* heap); void (*optimise) (GroundBytecodeProgram* program); diff --git a/meson.build b/meson.build index f55101d..cf0c7e3 100644 --- a/meson.build +++ b/meson.build @@ -6,6 +6,8 @@ sources = files( 'src/Bytecode/Heap/get.c', 'src/Bytecode/Heap/set.c', + 'src/Bytecode/save.c', + 'src/Bytecode/load.c', 'src/Bytecode/Instruction/execute.c', 'src/Bytecode/Program/execute.c', 'src/Bytecode/Program/optimise.c', @@ -99,6 +101,10 @@ sources = files( 'src/Struct/addField.c' ) +cli_sources = files( + 'src/cli/main.c' +) + incdir = include_directories('include') libffi = dependency('libffi', version : '>=3.0.0') @@ -112,3 +118,5 @@ pkg.generate(lib, version : meson.project_version(), description : 'Ground virtual machine', ) + +cli = executable('ground', cli_sources, include_directories : incdir, install : true, link_with : lib) diff --git a/src/Bytecode/Instruction/execute.c b/src/Bytecode/Instruction/execute.c index 3dfa8a5..10f4224 100644 --- a/src/Bytecode/Instruction/execute.c +++ b/src/Bytecode/Instruction/execute.c @@ -24,10 +24,14 @@ int64_t _GroundBytecodeInstructionExecute(GroundBytecodeInstruction* instruction goto *jumpTable[instruction->type]; IF: { + GroundValue* cond = Ground.Bytecode.Heap.get(heap, instruction->args.at[0]); + if (cond->as.Bool) { + return instruction->args.at[1]; + } return -1; } JUMP: { - return -1; + return instruction->args.at[0]; } END: { return -2; diff --git a/src/Bytecode/load.c b/src/Bytecode/load.c new file mode 100644 index 0000000..aca5995 --- /dev/null +++ b/src/Bytecode/load.c @@ -0,0 +1,141 @@ +#include "../../include/ground.h" +#include +#include + +static GroundValue readValue(FILE* f) { + GroundValue v = {0}; + uint8_t tag; + if (fread(&tag, 1, 1, f) != 1) { + Ground.Log.Error("failed to read value type in Ground.Bytecode.load"); + Ground.Flags.error = true; + return v; + } + + switch (tag) { + case 0: { + v.type.type = GroundType_Int; + fread(&v.as.Int, 8, 1, f); + break; + } + case 1: { + v.type.type = GroundType_Double; + fread(&v.as.Double, 8, 1, f); + break; + } + case 2: { + v.type.type = GroundType_Char; + fread(&v.as.Char, 1, 1, f); + break; + } + case 3: { + v.type.type = GroundType_Bool; + fread(&v.as.Bool, 1, 1, f); + break; + } + case 4: { + v.type.type = GroundType_String; + uint64_t len; + fread(&len, 8, 1, f); + v.as.String.cstr = malloc(len + 1); + if (v.as.String.cstr == NULL) { + Ground.Log.Error("malloc failed in Ground.Bytecode.load"); + Ground.Flags.error = true; + return v; + } + fread(v.as.String.cstr, 1, len, f); + v.as.String.cstr[len] = '\0'; + v.as.String.len = len; + break; + } + default: { + Ground.Log.Error("unknown value type in Ground.Bytecode.load"); + Ground.Flags.error = true; + break; + } + } + + return v; +} + +GroundBytecode _GroundBytecodeLoad(const char* path) { + GroundBytecode bc = {0}; + + FILE* f = fopen(path, "rb"); + if (f == NULL) { + Ground.Log.Error("failed to open file for reading in Ground.Bytecode.load"); + Ground.Flags.error = true; + return bc; + } + + char magic[8]; + memset(magic, 0, 8); + if (fread(magic, 7, 1, f) != 1 || memcmp(magic, "GRNDbc", 6) != 0) { + Ground.Log.Error("bad magic in Ground.Bytecode.load"); + Ground.Flags.error = true; + fclose(f); + return bc; + } + + uint32_t version; + fread(&version, 4, 1, f); + + uint64_t count; + fread(&count, 8, 1, f); + if (count > 0) { + bc.heap.heap = malloc(sizeof(GroundValue) * count); + if (bc.heap.heap == NULL) { + Ground.Log.Error("malloc failed in Ground.Bytecode.load"); + Ground.Flags.error = true; + fclose(f); + return bc; + } + for (uint64_t i = 0; i < count; i++) { + bc.heap.heap[i] = readValue(f); + if (Ground.Flags.error) { + fclose(f); + return bc; + } + } + bc.heap.capacity = count; + bc.heap.len = count; + } + + fread(&count, 8, 1, f); + if (count > 0) { + bc.program.at = malloc(sizeof(GroundBytecodeInstruction) * count); + if (bc.program.at == NULL) { + Ground.Log.Error("malloc failed in Ground.Bytecode.load"); + Ground.Flags.error = true; + fclose(f); + return bc; + } + bc.program.capacity = count; + bc.program.len = count; + + for (uint64_t i = 0; i < count; i++) { + uint8_t type; + fread(&type, 1, 1, f); + bc.program.at[i].type = (enum GroundInstructionType)type; + + uint64_t argCount; + fread(&argCount, 8, 1, f); + if (argCount > 0) { + bc.program.at[i].args.at = malloc(sizeof(GroundSize) * argCount); + if (bc.program.at[i].args.at == NULL) { + Ground.Log.Error("malloc failed in Ground.Bytecode.load"); + Ground.Flags.error = true; + fclose(f); + return bc; + } + fread(bc.program.at[i].args.at, sizeof(GroundSize), argCount, f); + } else { + bc.program.at[i].args.at = NULL; + } + bc.program.at[i].args.len = argCount; + bc.program.at[i].args.capacity = argCount; + } + } + + fclose(f); + return bc; +} diff --git a/src/Bytecode/save.c b/src/Bytecode/save.c new file mode 100644 index 0000000..2dc38bf --- /dev/null +++ b/src/Bytecode/save.c @@ -0,0 +1,75 @@ +#include "../../include/ground.h" +#include + +static void writeValue(FILE* f, GroundValue* v) { + switch (v->type.type) { + case GroundType_Int: { + uint8_t tag = 0; + fwrite(&tag, 1, 1, f); + fwrite(&v->as.Int, 8, 1, f); + break; + } + case GroundType_Double: { + uint8_t tag = 1; + fwrite(&tag, 1, 1, f); + fwrite(&v->as.Double, 8, 1, f); + break; + } + case GroundType_Char: { + uint8_t tag = 2; + fwrite(&tag, 1, 1, f); + fwrite(&v->as.Char, 1, 1, f); + break; + } + case GroundType_Bool: { + uint8_t tag = 3; + fwrite(&tag, 1, 1, f); + fwrite(&v->as.Bool, 1, 1, f); + break; + } + case GroundType_String: { + uint8_t tag = 4; + fwrite(&tag, 1, 1, f); + uint64_t len = v->as.String.len; + fwrite(&len, 8, 1, f); + fwrite(v->as.String.cstr, 1, len, f); + break; + } + default: { + uint8_t tag = 0xFF; + fwrite(&tag, 1, 1, f); + break; + } + } +} + +void _GroundBytecodeSave(GroundBytecode* bytecode, const char* path) { + FILE* f = fopen(path, "wb"); + if (f == NULL) { + Ground.Log.Error("failed to open file for writing in Ground.Bytecode.save"); + Ground.Flags.error = true; + return; + } + + fwrite("GRNDbc\0", 7, 1, f); + uint32_t version = 1; + fwrite(&version, 4, 1, f); + + uint64_t count = bytecode->heap.len; + fwrite(&count, 8, 1, f); + for (GroundSize i = 0; i < count; i++) { + writeValue(f, &bytecode->heap.heap[i]); + } + + count = bytecode->program.len; + fwrite(&count, 8, 1, f); + for (GroundSize i = 0; i < count; i++) { + uint8_t type = (uint8_t)bytecode->program.at[i].type; + fwrite(&type, 1, 1, f); + uint64_t argCount = bytecode->program.at[i].args.len; + fwrite(&argCount, 8, 1, f); + fwrite(bytecode->program.at[i].args.at, 8, argCount, f); + } + + fclose(f); +} diff --git a/src/New/Bytecode.c b/src/New/Bytecode.c index 78421ef..eee7654 100644 --- a/src/New/Bytecode.c +++ b/src/New/Bytecode.c @@ -72,12 +72,6 @@ static inline void doLabels(GroundProgram* program, GroundState* state) { } -static inline void addToGroundBytecodeProgram(GroundBytecodeProgram* program, GroundBytecodeInstruction inst) { - if (program->len + 1 >= program->capacity) { - - } -} - /* * Assigns an offset to each variable referenced. */ @@ -111,6 +105,7 @@ static inline GroundSize doOffsets(GroundProgram* gp, GroundState* state, Ground item->_offset = size++; snprintf(item->name, sizeof(item->name) - 1, "_._.ground_internal_constant_%zu", item->_offset); HASH_ADD_STR(state->variables, name, item); + arg->_offset = item->_offset; continue; } @@ -131,7 +126,6 @@ static inline GroundSize doOffsets(GroundProgram* gp, GroundState* state, Ground } arg->_offset = item->_offset; - size++; } // Convert to GroundBytecodeInstruction @@ -142,7 +136,11 @@ static inline GroundSize doOffsets(GroundProgram* gp, GroundState* state, Ground .args.capacity = gp->at[i].args.len }; - if (inst.args.at == NULL) {} + if (inst.args.at == NULL) { + Ground.Log.Error("malloc failed in Ground.Internal.run()"); + Ground.Flags.error = true; + return 0; + } for (GroundSize j = 0; j < gp->at[i].args.len; j++) { inst.args.at[j] = gp->at[i].args.at[j]._offset; @@ -196,12 +194,10 @@ GroundBytecode _GroundNewBytecode(GroundProgram* program, GroundState* state) { GroundVariable *s, *tmp; - // Copy constants into heap - GroundSize i = 0; + // Copy constants into heap at their assigned offsets HASH_ITER(hh, state->variables, s, tmp) { - bytecode.heap.heap[i] = Ground.Copy.Value(&s->value); + bytecode.heap.heap[s->_offset] = Ground.Copy.Value(&s->value); if (Ground.Flags.error) return bytecode; - i++; } diff --git a/src/cli/main.c b/src/cli/main.c new file mode 100644 index 0000000..49ad4e2 --- /dev/null +++ b/src/cli/main.c @@ -0,0 +1,107 @@ +#include "../../include/ground.h" +#include + +enum ArgsAction { + ARGS_HELP, ARGS_EXECUTE, ARGS_DEBUG, ARGS_ASSEMBLE, ARGS_DISASSEMBLE +}; + +typedef struct Args { + enum ArgsAction action; + char* inputFile; + char* outputFile; +} Args; + +Args parseArgs(int argc, char** argv) { + Args args = { + .action = ARGS_EXECUTE, + .inputFile = NULL, + .outputFile = NULL + }; + + if (argc == 1) { + args.action = ARGS_HELP; + return args; + } + + for (int i = 1; i < argc; i++) { + char* arg = argv[i]; + + if (strcmp(arg, "-d") == 0 || strcmp(arg, "--debug") == 0) { + args.action = ARGS_DEBUG; + } else if (strcmp(arg, "-a") == 0 || strcmp(arg, "--assemble") == 0) { + args.action = ARGS_ASSEMBLE; + if (i + i < argc) { + i++; + args.outputFile = argv[i]; + } + } else if (strcmp(arg, "-D") == 0 || strcmp(arg, "--disassemble") == 0) { + args.action = ARGS_DISASSEMBLE; + } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { + args.action = ARGS_HELP; + } else { + args.inputFile = arg; + } + + } + + return args; +} + +int main(int argc, char** argv) { + Args args = parseArgs(argc, argv); + + if (args.inputFile == NULL) { + fprintf(stderr, "Please specify a bytecode file\n"); + return 1; + } + + switch (args.action) { + case ARGS_EXECUTE: { + GroundBytecode bc = Ground.Bytecode.load(args.inputFile); + if (Ground.Flags.error) { + fprintf(stderr, "Failed to load bytecode, printing errors...\n"); + Ground.Log.printErrors(); + return 1; + } + + Ground.Bytecode.Program.execute(&bc.program, &bc.heap); + if (Ground.Flags.error) { + fprintf(stderr, "Failed to run program, printing errors...\n"); + Ground.Log.printErrors(); + return 1; + } + break; + } + case ARGS_DEBUG: { + fprintf(stderr, "Not yet implemented"); + break; + } + case ARGS_ASSEMBLE: { + fprintf(stderr, "Not yet implemented"); + break; + } + case ARGS_DISASSEMBLE: { + fprintf(stderr, "Not yet implemented"); + break; + } + case ARGS_HELP: { + printf("GroundVM cli help\n"); + printf("Usage: %s [-h] [--help] [-d] [--debug] [-a ] [--assemble ] [-D] [--disassemble]\n", argv[0]); + printf("Options:\n"); + printf(" -h or --help\n"); + printf(" Shows this help message\n"); + printf(" -d or --debug\n"); + printf(" Interactive debugger for a Ground program\n"); + printf(" -a or --assemble \n"); + printf(" Assembles a .grnd textual representation into a .gb bytecode file\n"); + printf(" Inputs from , outputs to \n"); + printf(" -D or --disassemble\n"); + printf(" Disassembles a .gb program, showing the heap, instructions, and offsets\n"); + printf("If no options are specified, the provided will be executed.\n"); + + break; + } + } + + return 0; +} diff --git a/src/libmain.c b/src/libmain.c index b372c80..4cf8434 100644 --- a/src/libmain.c +++ b/src/libmain.c @@ -100,6 +100,9 @@ void _GroundBytecodeProgramOptimise(GroundBytecodeProgram* program); int64_t _GroundBytecodeInstructionExecute(GroundBytecodeInstruction* instruction, GroundBytecodeHeap* heap); +void _GroundBytecodeSave(GroundBytecode* bytecode, const char* path); +GroundBytecode _GroundBytecodeLoad(const char* path); + void _GroundBytecodeHeapSet(GroundBytecodeHeap* heap, GroundSize idx, GroundValue value); GroundValue* _GroundBytecodeHeapGet(GroundBytecodeHeap* heap, GroundSize idx); @@ -223,6 +226,8 @@ struct _Ground Ground = { }, .Bytecode = { + .save = _GroundBytecodeSave, + .load = _GroundBytecodeLoad, .Program = { .execute = _GroundBytecodeProgramExecute, .optimise = _GroundBytecodeProgramOptimise, diff --git a/test/test b/test/test deleted file mode 100755 index afefbec..0000000 Binary files a/test/test and /dev/null differ diff --git a/test/test.c b/test/test.c deleted file mode 100644 index c01260d..0000000 --- a/test/test.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include - -int main() { - GroundProgram program = Ground.New.Program(); - GroundState state = {NULL, NULL, NULL}; - - // Instruction 1: CREATELABEL start - GroundInstruction label_inst = Ground.New.Instruction(GroundInstruction_CREATELABEL); - Ground.Instruction.append(&label_inst, Ground.New.Arg.LabelRef("start")); - Ground.Program.append(&program, label_inst); - - // Instruction 2: JUMP start - GroundInstruction jump_inst = Ground.New.Instruction(GroundInstruction_JUMP); - Ground.Instruction.append(&jump_inst, Ground.New.Arg.LabelRef("start")); - Ground.Program.append(&program, jump_inst); - - // Execute program - Ground.Program.execute(&program, &state); - - if (Ground.Flags.error) { - printf("Error set during execution!\n"); - Ground.Log.printErrors(); - return 1; - } else { - printf("Success!\n"); - return 0; - } -}