diff --git a/Makefile b/Makefile index 5d623346..8f4e88b4 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ TARGET=interp PREFIX=/usr/local # C sources -HEADERS=vm.h ds.h compile.h parse.h value.h datatypes.h gc.h util.h +HEADERS=vm.h ds.h compile.h parse.h value.h datatypes.h gc.h util.h gst.h SOURCES=main.c parse.c value.c vm.c ds.c compile.c gc.c OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) diff --git a/compile.h b/compile.h index 4b2f3d6c..0736b490 100644 --- a/compile.h +++ b/compile.h @@ -2,6 +2,20 @@ #define COMPILE_H_9VXF71HY #include "datatypes.h" +#include + +typedef struct GstCompiler GstCompiler; +typedef struct GstScope GstScope; + +/* Compilation state */ +struct GstCompiler { + Gst *vm; + const char *error; + jmp_buf onError; + GstScope *tail; + GstArray *env; + GstBuffer *buffer; +}; /* Initialize the Compiler */ void gst_compiler(GstCompiler *c, Gst *vm); @@ -15,10 +29,4 @@ void gst_compiler_add_global_cfunction(GstCompiler *c, const char *name, GstCFun /* Compile a function that evaluates the given form. */ GstFunction *gst_compiler_compile(GstCompiler *c, GstValue form); -/* Macro expansion. Macro expansion happens prior to the compilation process - * and is completely separate. This allows the compilation to not have to worry - * about garbage collection and other issues that would complicate both the - * runtime and the compilation. */ -int gst_macro_expand(Gst *vm, GstValue x, GstObject *macros, GstValue *out); - #endif /* end of include guard: COMPILE_H_9VXF71HY */ diff --git a/datatypes.h b/datatypes.h index 9445a8e1..3c1bf815 100644 --- a/datatypes.h +++ b/datatypes.h @@ -2,7 +2,6 @@ #define DATATYPES_H_PJJ035NT #include -#include /* Flag for immutability in an otherwise mutable datastructure */ #define GST_IMMUTABLE 1 @@ -36,19 +35,15 @@ typedef struct GstArray GstArray; typedef struct GstBuffer GstBuffer; typedef struct GstObject GstObject; typedef struct GstThread GstThread; -typedef GstValue (*GstCFunction)(Gst * vm); +typedef int (*GstCFunction)(Gst * vm); /* Implementation details */ -typedef struct GstParser GstParser; -typedef struct GstCompiler GstCompiler; typedef struct GstFuncDef GstFuncDef; typedef struct GstFuncEnv GstFuncEnv; /* Definitely implementation details */ typedef struct GstStackFrame GstStackFrame; -typedef struct GstParseState GstParseState; typedef struct GstBucket GstBucket; -typedef struct GstScope GstScope; /* The general gst value type. Contains a large union and * the type information of the value */ @@ -155,6 +150,11 @@ struct GstStackFrame { uint16_t *pc; }; +/* VM return status from c function */ +#define GST_RETURN_OK 0 +#define GST_RETURN_ERROR 1 +#define GST_RETURN_CRASH 2 + /* The VM state */ struct Gst { /* Garbage collection */ @@ -166,47 +166,9 @@ struct Gst { GstThread *thread; /* Return state */ const char *crash; - jmp_buf jump; - GstValue error; - GstValue ret; /* Returned value from VMStart. Also holds errors. */ + GstValue ret; /* Returned value from gst_start. Also holds errors. */ }; -struct GstParser { - Gst *vm; - const char *error; - GstParseState *data; - GstValue value; - uint32_t count; - uint32_t cap; - uint32_t index; - uint32_t flags; - enum { - GST_PARSER_PENDING = 0, - GST_PARSER_FULL, - GST_PARSER_ERROR - } status; -}; - -/* Compilation state */ -struct GstCompiler { - Gst *vm; - const char *error; - jmp_buf onError; - GstScope *tail; - GstArray *env; - GstBuffer *buffer; -}; - -/* String utils */ -#define gst_string_raw(s) ((uint32_t *)(s) - 2) -#define gst_string_length(v) (gst_string_raw(v)[0]) -#define gst_string_hash(v) (gst_string_raw(v)[1]) - -/* Tuple utils */ -#define gst_tuple_raw(s) ((uint32_t *)(s) - 2) -#define gst_tuple_length(v) (gst_tuple_raw(v)[0]) -#define gst_tuple_hash(v) (gst_tuple_raw(v)[1]) - /* Bytecode */ enum GstOpCode { GST_OP_ADD = 0, /* Addition */ diff --git a/gc.c b/gc.c index b029df16..582388c7 100644 --- a/gc.c +++ b/gc.c @@ -184,7 +184,7 @@ void gst_sweep(Gst *vm) { static void *gst_alloc_prepare(Gst *vm, char *rawBlock, uint32_t size) { GCMemoryHeader *mdata; if (rawBlock == NULL) { - gst_crash(vm, "out of memory"); + GST_OUT_OF_MEMORY; } vm->nextCollection += size; mdata = (GCMemoryHeader *)rawBlock; @@ -216,7 +216,6 @@ void gst_collect(Gst *vm) { gst_mark(vm, &thread); } gst_mark(vm, &vm->ret); - gst_mark(vm, &vm->error); gst_sweep(vm); vm->nextCollection = 0; } diff --git a/gst.h b/gst.h new file mode 100644 index 00000000..eeb9c4fc --- /dev/null +++ b/gst.h @@ -0,0 +1,12 @@ +#ifndef gst_h_INCLUDED +#define gst_h_INCLUDED + +#include "util.h" +#include "datatypes.h" +#include "vm.h" +#include "parse.h" +#include "compile.h" +#include "value.h" + +#endif // gst_h_INCLUDED + diff --git a/main.c b/main.c index ced00d59..2f54bb9e 100644 --- a/main.c +++ b/main.c @@ -1,10 +1,6 @@ #include #include -#include "datatypes.h" -#include "vm.h" -#include "parse.h" -#include "compile.h" -#include "value.h" +#include "gst.h" /* Simple printer for gst strings */ void string_put(FILE *out, uint8_t * string) { @@ -15,16 +11,14 @@ void string_put(FILE *out, uint8_t * string) { } /* Test c function */ -GstValue print(Gst *vm) { +int print(Gst *vm) { uint32_t j, count; - GstValue nil; count = gst_count_args(vm); for (j = 0; j < count; ++j) { string_put(stdout, gst_to_string(vm, gst_arg(vm, j))); fputc('\n', stdout); } - nil.type = GST_NIL; - return nil; + return GST_RETURN_OK; } /* A simple repl for debugging */ @@ -98,7 +92,7 @@ void debug_repl(FILE *in, FILE *out) { fprintf(out, "VM crash: %s\n", vm.crash); } else { fprintf(out, "VM error: "); - string_put(out, gst_to_string(&vm, vm.error)); + string_put(out, gst_to_string(&vm, vm.ret)); printf("\n"); } } diff --git a/parse.h b/parse.h index 2a9ac2bd..fcdf0e8a 100644 --- a/parse.h +++ b/parse.h @@ -3,6 +3,26 @@ #include "datatypes.h" +typedef struct GstParser GstParser; +typedef struct GstParseState GstParseState; + +/* Holds the parsing state */ +struct GstParser { + Gst *vm; + const char *error; + GstParseState *data; + GstValue value; + uint32_t count; + uint32_t cap; + uint32_t index; + uint32_t flags; + enum { + GST_PARSER_PENDING = 0, + GST_PARSER_FULL, + GST_PARSER_ERROR + } status; +}; + /* Some parser flags */ #define GST_PARSER_FLAG_INCOMMENT 1 #define GST_PARSER_FLAG_EXPECTING_COMMENT 2 diff --git a/util.h b/util.h index c22ae2be..2e50473c 100644 --- a/util.h +++ b/util.h @@ -1,6 +1,16 @@ #ifndef util_h_INCLUDED #define util_h_INCLUDED +/* String utils */ +#define gst_string_raw(s) ((uint32_t *)(s) - 2) +#define gst_string_length(v) (gst_string_raw(v)[0]) +#define gst_string_hash(v) (gst_string_raw(v)[1]) + +/* Tuple utils */ +#define gst_tuple_raw(s) ((uint32_t *)(s) - 2) +#define gst_tuple_length(v) (gst_tuple_raw(v)[0]) +#define gst_tuple_hash(v) (gst_tuple_raw(v)[1]) + /* Memcpy for moving memory */ #ifndef gst_memcpy #include @@ -36,5 +46,20 @@ #define NULL ((void *)0) #endif +/* C function helpers */ + +/* Return in a c function */ +#define gst_c_return(vm, x) (do { (vm)->ret = (x); return GST_RETURN_OK; } while (0)) + +/* Throw error from a c function */ +#define gst_c_throw(vm, e) (do { (vm)->ret = (e); return GST_RETURN_ERROR; } while (0)) + +/* What to do when out of memory */ +#ifndef GST_OUT_OF_MEMORY +#include +#include +#define GST_OUT_OF_MEMORY do { printf("out of memory.\n"); exit(1); } while (0) +#endif + #endif // util_h_INCLUDED diff --git a/value.c b/value.c index 3bb7a9f1..b07279a9 100644 --- a/value.c +++ b/value.c @@ -388,75 +388,78 @@ static uint8_t to_byte(GstNumber raw) { return (uint8_t) raw; } -/* Get a value out af an associated data structure. Can throw VM error. */ -GstValue gst_get(Gst *vm, GstValue ds, GstValue key) { +/* Get a value out af an associated data structure. + * Returns possible c error message, and NULL for no error. The + * useful return value is written to out on success */ +const char *gst_get(GstValue ds, GstValue key, GstValue *out) { int32_t index; GstValue ret; switch (ds.type) { case GST_ARRAY: - gst_assert_type(vm, key, GST_NUMBER); + if (key.type != GST_NUMBER) return "expected numeric key"; index = to_index(key.data.number, ds.data.array->count); - if (index == -1) gst_error(vm, "invalid array access"); + if (index == -1) return "invalid array access"; ret = ds.data.array->data[index]; break; case GST_TUPLE: - gst_assert_type(vm, key, GST_NUMBER); + if (key.type != GST_NUMBER) return "expected numeric key"; index = to_index(key.data.number, gst_tuple_length(ds.data.tuple)); - if (index < 0) gst_error(vm, "invalid tuple access"); + if (index < 0) return "invalid tuple access"; ret = ds.data.tuple[index]; break; case GST_BYTEBUFFER: - gst_assert_type(vm, key, GST_NUMBER); + if (key.type != GST_NUMBER) return "expected numeric key"; index = to_index(key.data.number, ds.data.buffer->count); - if (index == -1) gst_error(vm, "invalid buffer access"); + if (index == -1) return "invalid buffer access"; ret.type = GST_NUMBER; ret.data.number = ds.data.buffer->data[index]; break; case GST_STRING: - gst_assert_type(vm, key, GST_NUMBER); + if (key.type != GST_NUMBER) return "expected numeric key"; index = to_index(key.data.number, gst_string_length(ds.data.string)); - if (index == -1) gst_error(vm, "invalid string access"); + if (index == -1) return "invalid string access"; ret.type = GST_NUMBER; ret.data.number = ds.data.string[index]; break; case GST_OBJECT: - return gst_object_get(ds.data.object, key); + ret = gst_object_get(ds.data.object, key); + break; default: - gst_error(vm, "cannot get"); + return "cannot get"; } - return ret; + *out = ret; + return NULL; } -/* Set a value in an associative data structure. Can throw VM error. */ -void gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) { +/* Set a value in an associative data structure. Returns possible + * error message, and NULL if no error. */ +const char *gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) { int32_t index; switch (ds.type) { case GST_ARRAY: if (ds.data.array->flags & GST_IMMUTABLE) - goto immutable; - gst_assert_type(vm, key, GST_NUMBER); + return "cannot set immutable value"; + if (key.type != GST_NUMBER) return "expected numeric key"; index = to_index(key.data.number, ds.data.array->count); - if (index == -1) gst_error(vm, "invalid array access"); + if (index == -1) return "invalid array access"; ds.data.array->data[index] = value; break; case GST_BYTEBUFFER: if (ds.data.buffer->flags & GST_IMMUTABLE) - goto immutable; - gst_assert_type(vm, key, GST_NUMBER); - gst_assert_type(vm, value, GST_NUMBER); + return "cannot set immutable value"; + if (key.type != GST_NUMBER) return "expected numeric key"; + if (value.type != GST_NUMBER) return "expected numeric value"; index = to_index(key.data.number, ds.data.buffer->count); - if (index == -1) gst_error(vm, "invalid buffer access"); + if (index == -1) return "invalid buffer access"; ds.data.buffer->data[index] = to_byte(value.data.number); break; case GST_OBJECT: if (ds.data.object->flags & GST_IMMUTABLE) - goto immutable; + return "cannot set immutable value"; gst_object_put(vm, ds.data.object, key, value); break; default: - gst_error(vm, "cannot set"); + return "cannot set"; } - return; - immutable: - gst_error(vm, "cannot set immutable value"); + return NULL; } diff --git a/value.h b/value.h index cb90554f..8595870c 100644 --- a/value.h +++ b/value.h @@ -15,10 +15,10 @@ int gst_compare(GstValue x, GstValue y); int gst_equals(GstValue x, GstValue y); /* Get a value from an associative gst object. Can throw errors. */ -GstValue gst_get(Gst *vm, GstValue ds, GstValue key); +const char *gst_get(GstValue ds, GstValue key, GstValue *out); /* Set a value in an associative gst object. Can throw errors. */ -void gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value); +const char *gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value); /* Load a c style string into a gst value (copies data) */ GstValue gst_load_cstring(Gst *vm, const char *string); diff --git a/vm.c b/vm.c index 02661fae..dce4ff18 100644 --- a/vm.c +++ b/vm.c @@ -1,23 +1,28 @@ - #include "vm.h" #include "util.h" #include "value.h" #include "ds.h" #include "gc.h" +/* Macros for errors in the vm */ + +/* Exit from the VM normally */ +#define gst_exit(vm, r) return ((vm)->ret = (r), GST_RETURN_OK) + +/* Bail from the VM with an error string. */ +#define gst_error(vm, e) return ((vm)->ret = gst_load_cstring((vm), (e)), GST_RETURN_ERROR) + +/* Crash. Not catchable, unlike error. */ +#define gst_crash(vm, e) return ((vm)->crash = (e), GST_RETURN_CRASH) + +/* Error if the condition is false */ +#define gst_assert(vm, cond, e) do {if (!(cond)){gst_error((vm), (e));}} while (0) + static const char GST_NO_UPVALUE[] = "no upvalue"; static const char GST_EXPECTED_FUNCTION[] = "expected function"; static const char GST_EXPECTED_NUMBER_ROP[] = "expected right operand to be number"; static const char GST_EXPECTED_NUMBER_LOP[] = "expected left operand to be number"; -/* Get a literal */ -static GstValue gst_vm_literal(Gst *vm, GstFunction *fn, uint16_t index) { - if (index > fn->def->literalsLen) { - gst_error(vm, GST_NO_UPVALUE); - } - return fn->def->literals[index]; -} - /* Load a function into the VM. The function will be called with * no arguments when run */ static void gst_load(Gst *vm, GstValue callee) { @@ -61,53 +66,18 @@ int gst_start(Gst *vm, GstValue func) { GstStackFrame frame; GstValue temp, v1, v2; uint16_t *pc; + /* Load the callee */ gst_load(vm, func); + + /* Intialize local state */ thread = *vm->thread; stack = thread.data + thread.count; frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE)); pc = frame.pc; - /* Set jmp_buf to jump back to for return. */ - { - int n; - if ((n = setjmp(vm->jump))) { - /* Good return */ - if (n == 1) { - return 0; - } else if (n == 2) { - /* Error. */ - while (!frame.errorJump) { - /* Check for closure */ - if (frame.env) { - frame.env->thread = NULL; - frame.env->stackOffset = frame.size; - frame.env->values = gst_alloc(vm, sizeof(GstValue) * frame.size); - gst_memcpy(frame.env->values, - thread.data + thread.count, - frame.size * sizeof(GstValue)); - } - stack -= frame.prevSize + GST_FRAME_SIZE; - if (stack <= thread.data) { - thread.count = 0; - break; - } - frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE)); - } - if (thread.count < GST_FRAME_SIZE) - return n; - /* Jump to the error location */ - pc = frame.errorJump; - /* Set error */ - stack[frame.errorSlot] = vm->error; - } else { - /* Crash. just return */ - return n; - } - } - } - /* Main interpreter loop */ + mainloop: for (;;) { switch (*pc) { @@ -290,10 +260,16 @@ int gst_start(Gst *vm, GstValue func) { /* Call the function */ if (temp.type == GST_CFUNCTION) { /* Save current state to vm thread */ + int status; *((GstStackFrame *)(stack - GST_FRAME_SIZE)) = frame; *vm->thread = thread; - v2 = temp.data.cfunction(vm); - goto ret; + vm->ret.type = GST_NIL; + status = temp.data.cfunction(vm); + v1 = vm->ret; + if (status == GST_RETURN_OK) + goto ret; + else + goto vm_error; } else { for (; i < locals; ++i) stack[i].type = GST_NIL; @@ -308,7 +284,9 @@ int gst_start(Gst *vm, GstValue func) { case GST_OP_CST: /* Load constant value */ gst_assert(vm, frame.callee.type == GST_FUNCTION, GST_EXPECTED_FUNCTION); - stack[pc[1]] = gst_vm_literal(vm, frame.callee.data.function, pc[2]); + if (pc[2] > frame.callee.data.function->def->literalsLen) + gst_error(vm, GST_NO_UPVALUE); + stack[pc[1]] = frame.callee.data.function->def->literals[pc[2]]; pc += 3; break; @@ -343,7 +321,9 @@ int gst_start(Gst *vm, GstValue func) { frame.env->stackOffset = thread.count; frame.env->values = NULL; } - temp = gst_vm_literal(vm, frame.callee.data.function, pc[2]); + if (pc[2] > frame.callee.data.function->def->literalsLen) + gst_error(vm, GST_NO_UPVALUE); + temp = frame.callee.data.function->def->literals[pc[2]]; if (temp.type != GST_NIL) gst_error(vm, "cannot create closure"); fn = gst_alloc(vm, sizeof(GstFunction)); @@ -488,10 +468,16 @@ int gst_start(Gst *vm, GstValue func) { /* Call the function */ if (temp.type == GST_CFUNCTION) { /* Save current state to vm thread */ + int status; *((GstStackFrame *)(stack - GST_FRAME_SIZE)) = frame; *vm->thread = thread; - v2 = temp.data.cfunction(vm); - goto ret; + vm->ret.type = GST_NIL; + status = temp.data.cfunction(vm); + v1 = vm->ret; + if (status == GST_RETURN_OK) + goto ret; + else + goto vm_error; } else { pc = temp.data.function->def->byteCode; } @@ -502,29 +488,38 @@ int gst_start(Gst *vm, GstValue func) { v2.type = GST_NIL; goto ret; - case GST_OP_GET: - temp = gst_get(vm, stack[pc[2]], stack[pc[3]]); - stack[pc[1]] = temp; - pc += 4; - break; + case GST_OP_GET: /* Associative get */ + { + const char *err; + err = gst_get(stack[pc[2]], stack[pc[3]], stack + pc[1]); + if (err != NULL) + gst_error(vm, err); + pc += 4; + break; + } - case GST_OP_SET: - gst_set(vm, stack[pc[1]], stack[pc[2]], stack[pc[3]]); - pc += 4; - break; + case GST_OP_SET: /* Associative set */ + { + const char *err; + err = gst_set(vm, stack[pc[1]], stack[pc[2]], stack[pc[3]]); + if (err != NULL) + gst_error(vm, err); + pc += 4; + break; + } - case GST_OP_ERR: - vm->error = stack[pc[1]]; - longjmp(vm->jump, 2); + case GST_OP_ERR: /* Throw error */ + vm->ret = stack[pc[1]]; + goto vm_error; break; - case GST_OP_TRY: + case GST_OP_TRY: /* Begin try block */ frame.errorSlot = pc[1]; frame.errorJump = pc + *(uint32_t *)(pc + 2); pc += 4; break; - case GST_OP_UTY: + case GST_OP_UTY: /* End try block */ frame.errorJump = NULL; pc++; break; @@ -560,6 +555,36 @@ int gst_start(Gst *vm, GstValue func) { *vm->thread = thread; gst_maybe_collect(vm); } + + /* Handle errors from c functions and vm opcodes */ + vm_error: + while (!frame.errorJump) { + /* Check for closure */ + if (frame.env) { + frame.env->thread = NULL; + frame.env->stackOffset = frame.size; + frame.env->values = gst_alloc(vm, sizeof(GstValue) * frame.size); + gst_memcpy(frame.env->values, + thread.data + thread.count, + frame.size * sizeof(GstValue)); + } + stack -= frame.prevSize + GST_FRAME_SIZE; + if (stack <= thread.data) { + thread.count = 0; + break; + } + frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE)); + } + /* If we completely unwind the stack we just return */ + if (thread.count < GST_FRAME_SIZE) + return GST_RETURN_ERROR; + /* Jump to the error location */ + pc = frame.errorJump; + /* Set error */ + stack[frame.errorSlot] = vm->ret; + /* Resume vm */ + goto mainloop; + } /* Get an argument from the stack */ @@ -567,7 +592,11 @@ GstValue gst_arg(Gst *vm, uint16_t index) { GstValue *stack = vm->thread->data + vm->thread->count; GstStackFrame *frame = (GstStackFrame *)(stack - GST_FRAME_SIZE); uint16_t frameSize = frame->size; - gst_assert(vm, frameSize > index, "cannot get arg out of stack bounds"); + if (frameSize <= index) { + GstValue ret; + ret.type = GST_NIL; + return ret; + } return stack[index]; } @@ -576,7 +605,7 @@ void gst_set_arg(Gst* vm, uint16_t index, GstValue x) { GstValue *stack = vm->thread->data + vm->thread->count; GstStackFrame *frame = (GstStackFrame *)(stack - GST_FRAME_SIZE); uint16_t frameSize = frame->size; - gst_assert(vm, frameSize > index, "cannot set arg out of stack bounds"); + if (frameSize <= index) return; stack[index] = x; } @@ -590,7 +619,6 @@ uint16_t gst_count_args(Gst *vm) { /* Initialize the VM */ void gst_init(Gst *vm) { vm->ret.type = GST_NIL; - vm->error.type = GST_NIL; vm->crash = NULL; /* Garbage collection */ vm->blocks = NULL; diff --git a/vm.h b/vm.h index 698d40dc..ed863210 100644 --- a/vm.h +++ b/vm.h @@ -4,23 +4,6 @@ #include "datatypes.h" #include "value.h" -/* Exit from the VM normally */ -#define gst_exit(vm, r) ((vm)->ret = (r), longjmp((vm)->jump, 1)) - -/* Bail from the VM with an error string. */ -#define gst_error(vm, e) ((vm)->error = gst_load_cstring((vm), (e)), longjmp((vm)->jump, 2)) - -/* Crash. Not catchable, unlike error. */ -#define gst_crash(vm, e) ((vm)->crash = (e), longjmp((vm)->jump, 3)) - -/* Error if the condition is false */ -#define gst_assert(vm, cond, e) do \ - { if (!(cond)) { gst_error((vm), (e)); } } while (0) - -/* Type assertion */ -#define gst_assert_type(vm, f, t) \ - gst_assert((vm), (f).type == (t), "Expected a different type.") - /* Initialize the VM */ void gst_init(Gst * vm);