MBED port of https://github.com/ys1382/filagree . The only change is adding #define MBED
Diff: vm.c
- Revision:
- 0:1a89e28dea91
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm.c Wed May 30 21:13:01 2012 +0000 @@ -0,0 +1,1108 @@ +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include "util.h" +#include "serial.h" +#include "vm.h" +#include "variable.h" +#include "sys.h" + +#define VM_NAME "vm" + +struct variable *run(struct Context *context, struct byte_array *program, bool in_context); +struct variable *rhs_pop(struct Context *context); +static void dst(struct Context *context); +void src_size(struct Context *context, int32_t size); +void display_code(struct Context *context, struct byte_array *code); + + +#ifdef DEBUG + +#define VM_DEBUGPRINT(...) DEBUGPRINT(__VA_ARGS__ ); if (!context->runtime) return; + +void print_operand_stack(); + +#define INDENT context->indent++; +#define UNDENT context->indent--; + +#else // not DEBUG + +#define INDENT +#define UNDENT + +#define VM_DEBUGPRINT(...) + +#endif // not DEBUG + + +// assertions ////////////////////////////////////////////////////////////// + +jmp_buf trying; + +static void vm_exit() { + longjmp(trying, 1); +} + +void set_error(struct Context *context, const char *format, va_list list) +{ + if (!context) + return; + null_check(format); + const char *message = make_message(format, list); + context->error = variable_new_err(context, message); +} + +void *vm_exit_message(struct Context *context, const char *format, ...) +{ + // make error variable + va_list list; + va_start(list, format); + set_error(context, format, list); + va_end(list); + + vm_exit(); + return NULL; +} + +void vm_assert(struct Context *context, bool assertion, const char *format, ...) +{ + if (!assertion) { + + // make error variable + va_list list; + va_start(list, format); + set_error(context, format, list); + va_end(list); + + vm_exit(); + } +} + +void vm_null_check(struct Context *context, const void* p) { + vm_assert(context, p, "null pointer"); +} + +// state /////////////////////////////////////////////////////////////////// + +struct program_state { + // struct byte_array *code; + struct map *named_variables; + struct array *all_variables; + uint32_t pc; +}; + +struct program_state *program_state_new(struct Context *context) +{ + null_check(context); + struct program_state *state = (struct program_state*)malloc(sizeof(struct program_state)); + state->named_variables = map_new(); + state->all_variables = array_new(); + stack_push(context->program_stack, state); + return state; +} + +struct Context *vm_init() +{ + struct Context *context = (struct Context*)malloc(sizeof(struct Context)); + null_check(context); + context->program_stack = stack_new(); + context->operand_stack = stack_new(); + context->vm_exception = NULL; + context->runtime = true; + context->num_vars = 0; + context->rhs = stack_new(); + + struct variable *vm_var = func_map(context); + context->vm_state = program_state_new(context); + map_insert(context->vm_state->named_variables, byte_array_from_string(VM_NAME), vm_var); + + return context; +} + +// garbage collection ////////////////////////////////////////////////////// + +void mark(struct Context *context, struct variable *root) +{ + null_check(context); + if (root->map) { + const struct array *values = map_values(root->map); + for (int i=0; values && i<values->length; i++) + mark(context, (struct variable*)array_get(values, i)); + } + + root->marked = true; + switch (root->type) { + case VAR_INT: + case VAR_FLT: + case VAR_STR: + case VAR_FNC: + case VAR_MAP: + break; + case VAR_LST: + for (int i=0; i<root->list->length; i++) + mark(context, (struct variable*)array_get(root->list, i)); + break; + default: + vm_exit_message(context, "bad var type"); + break; + } +} + +void sweep(struct Context *context, struct variable *root) +{ + null_check(context); + struct program_state *state = (struct program_state*)stack_peek(context->program_stack, 0); + struct array *vars = state->all_variables; + for (int i=0; i<vars->length; i++) { + struct variable *v = (struct variable*)array_get(vars, i); + if (!v->marked) + variable_del(context, v); + else + v->marked = false; + } +} + +void garbage_collect(struct Context *context) +{ + null_check(context); + struct program_state *state = (struct program_state*)stack_peek(context->program_stack, 0); + struct array *vars = state->all_variables; + for (int i=0; i<vars->length; i++) { + struct variable *v = (struct variable*)array_get(vars, i); + mark(context, v); + sweep(context, v); + } +} + +// display ///////////////////////////////////////////////////////////////// + +#ifdef DEBUG + +const struct number_string opcodes[] = { + {VM_NIL, "NIL"}, + {VM_INT, "INT"}, + {VM_BOOL, "BUL"}, + {VM_FLT, "FLT"}, + {VM_STR, "STR"}, + {VM_VAR, "VAR"}, + {VM_FNC, "FNC"}, + {VM_SRC, "SRC"}, + {VM_LST, "LST"}, + {VM_DST, "DST"}, + {VM_MAP, "MAP"}, + {VM_GET, "GET"}, + {VM_PUT, "PUT"}, + {VM_ADD, "ADD"}, + {VM_SUB, "SUB"}, + {VM_MUL, "MUL"}, + {VM_DIV, "DIV"}, + {VM_MOD, "MOD"}, + {VM_AND, "AND"}, + {VM_OR, "ORR"}, + {VM_NOT, "NOT"}, + {VM_NEG, "NEG"}, + {VM_EQU, "EQU"}, + {VM_NEQ, "NEQ"}, + {VM_GTN, "GTN"}, + {VM_LTN, "LTN"}, + {VM_IFF, "IFF"}, + {VM_JMP, "JMP"}, + {VM_CAL, "CAL"}, + {VM_MET, "MET"}, + {VM_RET, "RET"}, + {VM_ITR, "ITR"}, + {VM_COM, "COM"}, + {VM_TRY, "TRY"}, +}; + +void print_operand_stack(struct Context *context) +{ + null_check(context); + struct variable *operand; + for (int i=0; (operand = stack_peek(context->operand_stack, i)); i++) + DEBUGPRINT("\t%s\n", variable_value_str(context, operand)); +} + +const char* indentation(struct Context *context) +{ + null_check(context); + static char str[100]; + int tab = 0; + while (tab < context->indent) + str[tab++] = '\t'; + str[tab] = 0; + return (const char*)str; +} + +static void display_program_counter(struct Context *context, const struct byte_array *program) +{ + null_check(context); + DEBUGPRINT("%s%2ld:%3d ", indentation(context), program->current-program->data, *program->current); +} + +void display_code(struct Context *context, struct byte_array *code) +{ + null_check(context); + bool was_running = context->runtime; + context->runtime = false; + + INDENT + run(context, code, false); + UNDENT + + context->runtime = was_running; +} + +void display_program(struct byte_array *program) +{ + struct Context *context = vm_init(); + + INDENT + DEBUGPRINT("%sprogram bytes:\n", indentation(context)); + + INDENT + for (int i=0; i<program->length; i++) + DEBUGPRINT("%s%2d:%3d\n", indentation(context), i, program->data[i]); + UNDENT + + DEBUGPRINT("%sprogram instructions:\n", indentation(context)); + byte_array_reset(program); + struct byte_array* code = serial_decode_string(program); + + display_code(context, code); + + UNDENT + UNDENT +} + +#else // not DEBUG + +void display_code(struct Context *context, struct byte_array *code) {} +const char* indentation(struct Context *context) { return ""; } + +#endif // DEBUG + +// instruction implementations ///////////////////////////////////////////// + +void src_size(struct Context *context, int32_t size) +{ + null_check(context); + if (size > 1) + while (stack_peek(context->rhs,1)) + stack_pop(context->rhs); + else if (!stack_empty(context->rhs)) + return; + + while (size--) + stack_push(context->rhs, variable_pop(context)); +} + +static void src(struct Context *context, enum Opcode op, struct byte_array *program) +{ + null_check(context); + int32_t size = serial_decode_int(program); + VM_DEBUGPRINT("%s %d\n", NUM_TO_STRING(opcodes, op), size); + src_size(context, size); +} + +void vm_call(struct Context *context) +{ + null_check(context); + // get the function pointer from the stack + struct variable *func = variable_pop(context); + INDENT + + // call the function + switch (func->type) { + case VAR_FNC: + run(context, func->str, false); + break; + case VAR_C: + func->cfnc(context); + break; + default: + vm_exit_message(context, "not a function"); + break; + } + UNDENT +} + +static inline void func_call(struct Context *context) +{ + null_check(context); + VM_DEBUGPRINT("VM_CAL\n"); + vm_call(context); +} + +static void push_list(struct Context *context, struct byte_array *program) +{ + null_check(context); + int32_t num_items = serial_decode_int(program); + DEBUGPRINT("LST %d", num_items); + if (!context->runtime) + VM_DEBUGPRINT("\n"); + struct array *items = array_new(); + struct map *map = map_new(); + + while (num_items--) { + struct variable* v = variable_pop(context); + if (v->type == VAR_MAP) + map_update(map, v->map); // mapped values are stored in the map, not list + else + array_insert(items, 0, v); + } + struct variable *list = variable_new_list(context, items); + list->map = map; + DEBUGPRINT(": %s\n", variable_value_str(context, list)); + variable_push(context, list); +} + +static void push_map(struct Context *context, struct byte_array *program) +{ + null_check(context); + int32_t num_items = serial_decode_int(program); + DEBUGPRINT("MAP %d", num_items); + if (!context->runtime) + VM_DEBUGPRINT("\n"); + struct map *map = map_new(); + while (num_items--) { + struct variable* value = variable_pop(context); + struct variable* key = variable_pop(context); + assert_message(key->type==VAR_STR, "non-string map index"); + map_insert(map, key->str, value); + } + struct variable *v = variable_new_map(context, map); + DEBUGPRINT(": %s\n", variable_value_str(context, v)); + variable_push(context, v); +} + +struct variable* variable_set(struct Context *context, struct variable *u, const struct variable* v) +{ + null_check(context); + vm_null_check(context, u); + vm_null_check(context, v); + switch (v->type) { + case VAR_NIL: break; + case VAR_INT: u->integer = v->integer; break; + case VAR_FLT: u->floater = v->floater; break; + case VAR_FNC: + case VAR_STR: u->str = byte_array_copy(v->str); break; + case VAR_LST: + u->list = v->list; + u->list->current = u->list->data; break; + default: + vm_exit_message(context, "bad var type"); + break; + } + if (v->type == VAR_STR) + u->str = byte_array_copy(v->str); + u->map = v->map; + return u; +} + +struct variable* variable_copy(struct Context *context, const struct variable* v) +{ + null_check(context); + vm_null_check(context, v); + struct variable *u = variable_new(context, (enum VarType)v->type); + variable_set(context, u, v); + return u; +} + + +// run ///////////////////////////////////////////////////////////////////// + +static struct variable *list_get_int(struct Context *context, + const struct variable *indexable, + const struct variable *index) +{ + null_check(context); + null_check(indexable); + null_check(index); + + uint32_t n = index->integer; + enum VarType it = (enum VarType)indexable->type; + switch (it) { + case VAR_LST: + return (struct variable*)array_get(indexable->list, n); + case VAR_STR: { + vm_assert(context, n<indexable->str->length, "index out of bounds"); + char *str = (char*)malloc(2); + sprintf(str, "%c", indexable->str->data[n]); + return variable_new_str(context, byte_array_from_string(str)); + } + default: + vm_exit_message(context, "indexing non-indexable"); + return NULL; + } +} + +void lookup(struct Context *context, struct variable *indexable, struct variable *index) +{ + null_check(context); + if (!context->runtime) + VM_DEBUGPRINT("GET\n"); + + struct variable *item=0; + + switch (index->type) { + case VAR_INT: + item = list_get_int(context, indexable, index); + break; + case VAR_STR: + if (indexable->map) + item = (struct variable*)map_get(indexable->map, index->str); + if (!item) + item = builtin_method(context, indexable, index); + assert_message(item, "did not find member"); + break; + default: + vm_exit_message(context, "bad index type"); + break; + } + DEBUGPRINT("%s\n", variable_value_str(context, item)); + variable_push(context, item); +} + +static void list_get(struct Context *context) +{ + null_check(context); + DEBUGPRINT("GET "); + if (!context->runtime) + VM_DEBUGPRINT("\n"); + struct variable *indexable, *index; + indexable = variable_pop(context); + index = variable_pop(context); + lookup(context, indexable, index); +} + +static void method(struct Context *context, struct byte_array *program) +{ + null_check(context); + DEBUGPRINT("MET "); + if (!context->runtime) + VM_DEBUGPRINT("\n"); + struct variable *indexable, *index; + indexable = variable_pop(context); + index = variable_pop(context); + lookup(context, indexable, index); + stack_push(context->rhs, indexable); + vm_call(context); +} + + +static int32_t jump(struct Context *context, struct byte_array *program) +{ + null_check(context); + null_check(program); + uint8_t *start = program->current; + int32_t offset = serial_decode_int(program); + DEBUGPRINT("JMP %d\n", offset); + if (!context->runtime) + return 0; + + return offset - (program->current - start); +} + +bool test_operand(struct Context *context) +{ + null_check(context); + struct variable* v = variable_pop(context); + bool indeed = false; + switch (v->type) { + case VAR_NIL: indeed = false; break; + case VAR_BOOL: indeed = v->boolean; break; + case VAR_INT: indeed = v->integer; break; + default: + vm_exit_message(context, "bad iff operand"); + break; + } + return indeed; +} + +static int32_t iff(struct Context *context, struct byte_array *program) +{ + null_check(context); + null_check(program); + int32_t offset = serial_decode_int(program); + DEBUGPRINT("IF %d\n", offset); + if (!context->runtime) + return 0; + return test_operand(context) ? 0 : (VOID_INT)offset; +} + +static void push_nil(struct Context *context) +{ + null_check(context); + struct variable* var = variable_new_nil(context); + VM_DEBUGPRINT("NIL\n"); + variable_push(context, var); +} + +static void push_int(struct Context *context, struct byte_array *program) +{ + null_check(context); + null_check(program); + int32_t num = serial_decode_int(program); + VM_DEBUGPRINT("INT %d\n", num); + struct variable* var = variable_new_int(context, num); + variable_push(context, var); +} + +static void push_bool(struct Context *context, struct byte_array *program) +{ + null_check(context); + null_check(program); + int32_t num = serial_decode_int(program); + VM_DEBUGPRINT("BOOL %d\n", num); + struct variable* var = variable_new_bool(context, num); + variable_push(context, var); +} + +static void push_float(struct Context *context, struct byte_array *program) +{ + null_check(context); + null_check(program); + float num = serial_decode_float(program); + VM_DEBUGPRINT("FLT %f\n", num); + struct variable* var = variable_new_float(context, num); + variable_push(context, var); +} + +struct variable *find_var(struct Context *context, const struct byte_array *name) +{ + null_check(context); + null_check(name); + const struct program_state *state = (const struct program_state*)stack_peek(context->program_stack, 0); + struct map *var_map = state->named_variables; + struct variable *v = (struct variable*)map_get(var_map, name); + //DEBUGPRINT("find_var(%s) in %p,%p = %p\n", byte_array_to_string(name), state, var_map, v); + if (!v) + v = (struct variable*)map_get(context->vm_state->named_variables, name); + return v; +} + +static void push_var(struct Context *context, struct byte_array *program) +{ + struct byte_array* name = serial_decode_string(program); + VM_DEBUGPRINT("VAR %s\n", byte_array_to_string(name)); + struct variable *v = find_var(context, name); + vm_assert(context, v, "variable %s not found", byte_array_to_string(name)); + variable_push(context, v); +} + +static void push_str(struct Context *context, struct byte_array *program) +{ + struct byte_array* str = serial_decode_string(program); + VM_DEBUGPRINT("STR '%s'\n", byte_array_to_string(str)); + struct variable* v = variable_new_str(context, str); + variable_push(context, v); +} + +static void push_fnc(struct Context *context, struct byte_array *program) +{ + uint32_t fcodelen = serial_decode_int(program); + struct byte_array* fbody = byte_array_new_size(fcodelen); + memcpy(fbody->data, program->current, fcodelen); + + DEBUGPRINT("FNC %u\n", fcodelen); + display_code(context, fbody); + + if (context->runtime) { + struct variable* var = variable_new_fnc(context, (struct byte_array*)fbody); + variable_push(context, var); + } + + program->current += fcodelen; +} + +void set_named_variable(struct Context *context, + struct program_state *state, + const struct byte_array *name, + const struct variable *value) +{ + if (!state) + state = (struct program_state*)stack_peek(context->program_stack, 0); + struct map *var_map = state->named_variables; + struct variable *to_var = find_var(context, name); + + if (!to_var) { // new variable + to_var = variable_copy(context, value); + to_var->name = byte_array_copy(name); + } else + variable_set(context, to_var, value); + + map_insert(var_map, name, to_var); + + //DEBUGPRINT(" (SET %s to %s in {%p,%p,%p})\n", byte_array_to_string(name), variable_value(to_var), state, var_map, to_var); +} + +struct variable *rhs_pop(struct Context *context) +{ + struct variable *value = (struct variable*)stack_pop(context->rhs); + if (!value) + value = variable_new_nil(context); + return value; +} + +static void set(struct Context *context, struct program_state *state, struct byte_array *program) +{ + null_check(context); + struct byte_array *name = serial_decode_string(program); // destination variable name + if (!context->runtime) + VM_DEBUGPRINT("SET %s\n", byte_array_to_string(name)); + + struct variable *value = rhs_pop(context); + DEBUGPRINT("SET %s to %s\n", byte_array_to_string(name), variable_value_str(context, value)); + set_named_variable(context, state, name, value); // set the variable to the value +} + +static void dst(struct Context *context) +{ + VM_DEBUGPRINT("DST\n"); + while (!stack_empty(context->rhs)) + stack_pop(context->rhs); +} + +static void list_put(struct Context *context) +{ + DEBUGPRINT("PUT"); + if (!context->runtime) + VM_DEBUGPRINT("\n"); + struct variable* recipient = variable_pop(context); + struct variable* key = variable_pop(context); + struct variable *value = rhs_pop(context); + + switch (key->type) { + case VAR_INT: + switch (recipient->type) { + case VAR_LST: + array_set(recipient->list, key->integer, value); + break; + case VAR_STR: + ((uint8_t*)recipient->str)[key->integer] = (uint8_t)value->integer; + break; + default: + vm_exit_message(context, "indexing non-indexable"); + } break; + case VAR_STR: + if (!recipient->map) + recipient->map = map_new(); + map_insert(recipient->map, key->str, value); + break; + default: + vm_exit_message(context, "bad index type"); + break; + } + DEBUGPRINT(": %s\n", variable_value_str(context, recipient)); +} + +static struct variable *binary_op_int(struct Context *context, + enum Opcode op, + const struct variable *u, + const struct variable *v) +{ + int32_t m = u->integer; + int32_t n = v->integer; + int32_t i; + switch (op) { + case VM_MUL: i = m * n; break; + case VM_DIV: i = m / n; break; + case VM_ADD: i = m + n; break; + case VM_SUB: i = m - n; break; + case VM_AND: i = m && n; break; + case VM_EQU: i = m == n; break; + case VM_OR: i = m || n; break; + case VM_GTN: i = m > n; break; + case VM_LTN: i = m < n; break; + case VM_BND: i = m & n; break; + case VM_BOR: i = m | n; break; + case VM_MOD: i = m % n; break; + case VM_XOR: i = m ^ n; break; + case VM_RSF: i = m >> n; break; + case VM_LSF: i = m << n; break; + + default: + return (struct variable*)vm_exit_message(context, "bad math int operator"); + } + return variable_new_int(context, i); +} + +static struct variable *binary_op_float(struct Context *context, + enum Opcode op, + const struct variable *u, + const struct variable *v) +{ + float m = u->floater; + float n = v->floater; + float f = 0; + switch (op) { + case VM_MUL: f = m * n; break; + case VM_DIV: f = m / n; break; + case VM_ADD: f = m + n; break; + case VM_SUB: f = m - n; break; + case VM_NEQ: f = m != n; break; + case VM_GTN: return variable_new_int(context, n > m); + case VM_LTN: return variable_new_int(context, n < m); + default: + return (struct variable*)vm_exit_message(context, "bad math float operator"); + } + return variable_new_float(context, f); +} + +static bool is_num(enum VarType vt) { + return vt == VAR_INT || vt == VAR_FLT; +} + +static struct variable *binary_op_str(struct Context *context, + enum Opcode op, + const struct variable *u, + const struct variable *v) +{ + null_check(context); + struct variable *w = NULL; + struct byte_array *ustr = variable_value(context, u); + struct byte_array *vstr = variable_value(context, v); + + switch (op) { + case VM_ADD: w = variable_new_str(context, byte_array_concatenate(2, vstr, ustr)); break; + case VM_EQU: w = variable_new_int(context, byte_array_equals(ustr, vstr)); break; + default: + return (struct variable*)vm_exit_message(context, "unknown string operation"); + } + return w; +} + +static bool variable_compare(struct Context *context, + const struct variable *u, + const struct variable *v) +{ + null_check(context); + if (!u != !v) + return false; + enum VarType ut = (enum VarType)u->type; + enum VarType vt = (enum VarType)v->type; + + if (ut != vt) + return false; + + switch (ut) { + case VAR_LST: + if (u->list->length != v->list->length) + return false; + for (int i=0; i<u->list->length; i++) { + struct variable *ui = (struct variable*)array_get(u->list, i); + struct variable *vi = (struct variable*)array_get(v->list, i); + if (!variable_compare(context, ui, vi)) + return false; + } + // for list, check the map too + case VAR_MAP: { + struct array *keys = map_keys(u->map); + for (int i=0; i<keys->length; i++) { + struct byte_array *key = (struct byte_array*)array_get(keys, i); + struct variable *uvalue = (struct variable*)map_get(u->map, key); + struct variable *vvalue = (struct variable*)map_get(v->map, key); + if (!variable_compare(context, uvalue, vvalue)) + return false; + } + return true; } + case VAR_INT: + return u->integer == v->integer; + case VAR_FLT: + return u->floater == v->floater; + case VAR_STR: + return byte_array_equals(u->str, v->str); + default: + return (bool)vm_exit_message(context, "bad comparison"); + } +} + +static struct variable *binary_op_lst(struct Context *context, + enum Opcode op, + const struct variable *u, + const struct variable *v) +{ + null_check(context); + vm_assert(context, u->type==VAR_LST && v->type==VAR_LST, "list op with non-lists"); + struct variable *w = NULL; + + switch (op) { + case VM_ADD: + w = variable_copy(context, v); + for (int i=0; i<u->list->length; i++) + array_add(w->list, array_get(u->list, i)); + map_update(w->map, u->map); + break; + default: + return (struct variable*)vm_exit_message(context, "unknown string operation"); + } + + return w; +} + +static void binary_op(struct Context *context, enum Opcode op) +{ + null_check(context); + if (!context->runtime) + VM_DEBUGPRINT("%s\n", NUM_TO_STRING(opcodes, op)); + + const struct variable *u = variable_pop(context); + const struct variable *v = variable_pop(context); + struct variable *w; + + if (op == VM_EQU) { + bool same = variable_compare(context, u, v); + w = variable_new_int(context, same); + } else { + + enum VarType ut = (enum VarType)u->type; + enum VarType vt = (enum VarType)v->type; + bool floater = (ut == VAR_FLT && is_num(vt)) || (vt == VAR_FLT && is_num(ut)); + + if (vt == VAR_STR || ut == VAR_STR) w = binary_op_str(context, op, u, v); + else if (floater) w = binary_op_float(context, op, u, v); + else if (ut == VAR_INT && vt == VAR_INT) w = binary_op_int(context, op, v, u); + else if (vt == VAR_LST) w = binary_op_lst(context, op, u, v); + else + vm_exit_message(context, "unknown binary op"); + } + + variable_push(context, w); + + DEBUGPRINT("%s(%s,%s) = %s\n", + NUM_TO_STRING(opcodes, op), + variable_value_str(context, v), + variable_value_str(context, u), + variable_value_str(context, w)); +} + +static void unary_op(struct Context *context, enum Opcode op) +{ + null_check(context); + if (!context->runtime) + VM_DEBUGPRINT("%s\n", NUM_TO_STRING(opcodes, op)); + + struct variable *v = (struct variable*)variable_pop(context); + struct variable *result = NULL; + + switch (v->type) { + case VAR_NIL: + { + switch (op) { + case VM_NEG: result = variable_new_nil(context); break; + case VM_NOT: result = variable_new_bool(context, true); break; + default: vm_exit_message(context, "bad math operator"); break; + } + } break; + case VAR_INT: { + int32_t n = v->integer; + switch (op) { + case VM_NEG: result = variable_new_int(context, -n); break; + case VM_NOT: result = variable_new_bool(context, !n); break; + case VM_INV: result = variable_new_int(context, ~n); break; + default: vm_exit_message(context, "bad math operator"); break; + } + } break; + default: + vm_exit_message(context, "bad math type"); + break; + } + + variable_push(context, result); + + DEBUGPRINT("%s(%s) = %s\n", + NUM_TO_STRING(opcodes, op), + variable_value_str(context, v), + variable_value_str(context, result)); +} + +// FOR who IN what WHERE where DO how +static void iterate(struct Context *context, + enum Opcode op, + struct program_state *state, + struct byte_array *program) +{ + null_check(context); + struct byte_array *who = serial_decode_string(program); + struct byte_array *where = serial_decode_string(program); + struct byte_array *how = serial_decode_string(program); + +#ifdef DEBUG + DEBUGPRINT("%s %s\n", + NUM_TO_STRING(opcodes, op), + byte_array_to_string(who)); + if (!context->runtime) { + if (where) { + DEBUGPRINT("%s\tWHERE\n", indentation(context)); + display_code(context, where); + } + DEBUGPRINT("%s\tDO\n", indentation(context)); + display_code(context, how); + return; + } +#endif + + bool comprehending = (op == VM_COM); + struct variable *result = comprehending ? variable_new_list(context, NULL) : NULL; + + struct variable *what = variable_pop(context); + for (int i=0; i<what->list->length; i++) { + + struct variable *that = (struct variable*)array_get(what->list, i); + set_named_variable(context, state, who, that); + + byte_array_reset(where); + byte_array_reset(how); + run(context, where, true); + if (test_operand(context)) { + + run(context, how, true); + + if (comprehending) { + struct variable *item = (struct variable*)stack_pop(context->operand_stack); + array_add(result->list, item); + } + } + } + + if (comprehending) + stack_push(context->operand_stack, result); +} + +static inline void vm_trycatch(struct Context *context, struct byte_array *program) +{ + struct byte_array *trial = serial_decode_string(program); + DEBUGPRINT("TRY %d\n", trial->length); + display_code(context, trial); + struct byte_array *name = serial_decode_string(program); + struct byte_array *catcher = serial_decode_string(program); + DEBUGPRINT("%sCATCH %s %d\n", indentation(context), byte_array_to_string(name), catcher->length); + display_code(context, catcher); + if (!context->runtime) + return; + + run(context, trial, true); + if (context->vm_exception) { + set_named_variable(context, NULL, name, context->vm_exception); + context->vm_exception = NULL; + run(context, catcher, true); + } +} + +struct variable *run(struct Context *context, struct byte_array *program, bool in_context) +{ + null_check(context); + struct program_state *state = NULL; + program->current = program->data; + if (context->runtime) { + if (in_context) + state = (struct program_state*)stack_peek(context->program_stack, 0); + else + state = program_state_new(context); + } + //DEBUGPRINT("run %d %d %p\n", runtime, context, state); + //DEBUGPRINT("\t%p < %p + %d? %s\n", program->current, program->data, program->length, program->current < program->data + program->length ? "yes":"no"); + + while (program->current < program->data + program->length) { + enum Opcode inst = (enum Opcode)*program->current; +#ifdef DEBUG + display_program_counter(context, program); +#endif + program->current++; // increment past the instruction + + if (inst == VM_RET) { + src(context, inst, program); + break; + } else if (inst == VM_TRO) { + DEBUGPRINT("THROW\n"); + if (context->runtime) { + context->vm_exception = (struct variable*)stack_pop(context->operand_stack); + break; + } else + continue; + } + + int32_t pc_offset = 0; + + switch (inst) { + case VM_MUL: + case VM_DIV: + case VM_ADD: + case VM_SUB: + case VM_AND: + case VM_EQU: + case VM_NEQ: + case VM_GTN: + case VM_LTN: + case VM_BND: + case VM_BOR: + case VM_MOD: + case VM_XOR: + case VM_INV: + case VM_RSF: + case VM_LSF: + case VM_OR: binary_op(context, inst); break; + case VM_NEG: + case VM_NOT: unary_op(context, inst); break; + case VM_SRC: src(context, inst, program); break; + case VM_DST: dst(context); break; + case VM_SET: set(context, state, program); break; + case VM_JMP: pc_offset = jump(context, program); break; + case VM_IFF: pc_offset = iff(context, program); break; + case VM_CAL: func_call(context); break; + case VM_LST: push_list(context, program); break; + case VM_MAP: push_map(context, program); break; + case VM_GET: list_get(context); break; + case VM_PUT: list_put(context); break; + case VM_NIL: push_nil(context); break; + case VM_INT: push_int(context, program); break; + case VM_FLT: push_float(context, program); break; + case VM_BOOL: push_bool(context, program); break; + case VM_STR: push_str(context, program); break; + case VM_VAR: push_var(context, program); break; + case VM_FNC: push_fnc(context, program); break; + case VM_MET: method(context, program); break; + case VM_COM: + case VM_ITR: iterate(context, inst, state, program); break; + case VM_TRY: vm_trycatch(context, program); break; + default: + return (struct variable*)vm_exit_message(context, ERROR_OPCODE); + } + program->current += pc_offset; + } + + //DEBUGPRINT("run done %d %d %p\n", runtime, context, state); + if (!context->runtime) + return NULL; + if (!in_context) + stack_pop(context->program_stack); + return (struct variable*)stack_peek(context->operand_stack, 0); +} + +struct variable *execute(struct byte_array *program, + bool in_context, + bridge *callback_to_c) +{ + null_check(program); + DEBUGPRINT("execute:\n"); + struct Context *context = vm_init(); + context->callback2c = callback_to_c; + vm_assert(context, program!=0 && program->data!=0, ERROR_NULL); + byte_array_reset(program); + struct byte_array* code = serial_decode_string(program); + +#ifdef DEBUG + context->indent = 1; +#endif + + struct variable *v; + if (!setjmp(trying)) + v = run(context, code, in_context); + else + v = context->error; + + return v; +}