diff --git a/.gitignore b/.gitignore index 3fec32c..eb0b35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ tmp/ +test.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9944fec --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# Compiler & flags +CC = gcc +CFLAGS = -Wall -Wextra -g -Isrc +SRC = src/vm.c src/bytecode.c src/value.c +OBJ = $(SRC:.c=.o) + +# Tests +TEST_SRC = src/test.c src/test_opcodes.c +TEST_BIN = $(TEST_SRC:src/%.c=%) + +.PHONY: all test clean + +# Default: build main demo +all: main + +main: $(OBJ) src/main.c + $(CC) $(CFLAGS) -o $@ $^ + +# Compile object files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Test target +tests: $(OBJ) $(TEST_BIN) + @for t in $(TEST_BIN); do \ + echo "Running $$t..."; \ + ./$$t; \ + echo "--------------------------------"; \ + done + +# Build each test executable +$(TEST_BIN): %: src/%.c $(OBJ) + $(CC) $(CFLAGS) -o $@ $(OBJ) $< + +# Clean everything +clean: + rm -f $(OBJ) fun $(TEST_BIN) + diff --git a/src/bytecode.c b/src/bytecode.c new file mode 100644 index 0000000..5ac8ad9 --- /dev/null +++ b/src/bytecode.c @@ -0,0 +1,43 @@ +#include "bytecode.h" +#include +#include +#include + +Bytecode *bytecode_new(void) { + Bytecode *bc = (Bytecode*)malloc(sizeof(Bytecode)); + bc->instructions = NULL; + bc->instr_count = 0; + bc->constants = NULL; + bc->const_count = 0; + return bc; +} + +int bytecode_add_constant(Bytecode *bc, Value v) { + bc->constants = (Value*)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1)); + bc->constants[bc->const_count] = copy_value(&v); + return bc->const_count++; +} + +int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) { + bc->instructions = (Instruction*)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1)); + bc->instructions[bc->instr_count].op = op; + bc->instructions[bc->instr_count].operand = operand; + return bc->instr_count++; +} + +void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) { + if (idx >= 0 && idx < bc->instr_count) { + bc->instructions[idx].operand = operand; + } +} + +void bytecode_free(Bytecode *bc) { + if (!bc) return; + for (int i = 0; i < bc->const_count; ++i) { + free_value(bc->constants[i]); + } + free(bc->constants); + free(bc->instructions); + free(bc); +} + diff --git a/src/bytecode.h b/src/bytecode.h new file mode 100644 index 0000000..108035d --- /dev/null +++ b/src/bytecode.h @@ -0,0 +1,70 @@ +#ifndef FUN_BYTECODE_H +#define FUN_BYTECODE_H + +#include +#include "value.h" + +// VM opcodes +typedef enum { + OP_NOP, + OP_LOAD_CONST, // operand = constant index + OP_LOAD_LOCAL, // operand = local slot index + OP_STORE_LOCAL, // operand = local slot index + + OP_LOAD_GLOBAL, // + OP_STORE_GLOBAL, // + + OP_ADD, // + OP_SUB, // + OP_MUL, // + OP_DIV, // + + OP_LT, // a < b -> push 1/0 + OP_LTE, // a <= b -> push 1/0 + OP_GT, // a > b -> push 1/0 + OP_GTE, // a >= b -> push 1/0 + OP_EQ, // a == b -> push 1/0 + OP_NEQ, // a != b -> push 1/0 + + OP_POP, // discard top of stack + OP_JUMP, // unconditional jump + OP_JUMP_IF_FALSE, // jump if top of stack is false (0) + + OP_CALL, // operand = arg count; pops fn + args and enters fn + OP_RETURN, // pop optional return value and return to caller + + OP_PRINT, + OP_HALT, + + // add after existing opcodes + OP_MOD, // a % b + OP_AND, // logical AND + OP_OR, // logical OR + OP_NOT, // logical NOT + + OP_DUP, // duplicate top of stack + OP_SWAP, // swap top two stack values +} OpCode; + +typedef struct { + OpCode op; + int32_t operand; +} Instruction; + +typedef struct Bytecode { + Instruction *instructions; + int instr_count; + + Value *constants; + int const_count; +} Bytecode; + +// constructors / manipulation +Bytecode *bytecode_new(void); +int bytecode_add_constant(Bytecode *bc, Value v); /* stores copy */ +int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand); +void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand); /* patching */ +void bytecode_free(Bytecode *bc); + +#endif + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..6fba101 --- /dev/null +++ b/src/main.c @@ -0,0 +1,52 @@ +#include "bytecode.h" +#include "value.h" +#include "vm.h" +#include + +int main(void) { + VM vm; + vm_init(&vm); + + /* Example: 0..4 loop */ + Bytecode *bc = bytecode_new(); + int c0 = bytecode_add_constant(bc, make_int(0)); + int c1 = bytecode_add_constant(bc, make_int(1)); + int c5 = bytecode_add_constant(bc, make_int(5)); + + // initialize i = 0 + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, 0); + + int lbl_start = bc->instr_count; + + // load i + bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // i = i + 1 + bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, 0); + + // check i < 5 + bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c5); + bytecode_add_instruction(bc, OP_LT, 0); + int jmp_back = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + bytecode_add_instruction(bc, OP_JUMP, lbl_start); + + int end_lbl = bc->instr_count; + bytecode_set_operand(bc, jmp_back, end_lbl); + + bytecode_add_instruction(bc, OP_HALT, 0); + + vm_run(&vm, bc); + + // flush captured outputs + vm_clear_output(&vm); + + bytecode_free(bc); + return 0; +} + diff --git a/src/opcode_names.c b/src/opcode_names.c new file mode 100644 index 0000000..8725e95 --- /dev/null +++ b/src/opcode_names.c @@ -0,0 +1,12 @@ +#include "vm.h" + +const char *opcode_names[] = { + "OP_NOP", "OP_LOAD_CONST", "OP_LOAD_LOCAL", "OP_STORE_LOCAL", + "OP_LOAD_GLOBAL", "OP_STORE_GLOBAL", "OP_ADD", "OP_SUB", + "OP_MUL", "OP_DIV", "OP_LT", "OP_LTE", + "OP_GT", "OP_GTE", "OP_EQ", "OP_NEQ", + "OP_POP", "OP_JUMP", "OP_JUMP_IF_FALSE", "OP_CALL", + "OP_RETURN", "OP_PRINT", "OP_MOD", "OP_AND", + "OP_OR", "OP_NOT", "OP_DUP", "OP_SWAP" +}; + diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..e69de29 diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..e69de29 diff --git a/src/test_opcodes.c b/src/test_opcodes.c new file mode 100644 index 0000000..2914857 --- /dev/null +++ b/src/test_opcodes.c @@ -0,0 +1,36 @@ +#include "vm.h" +#include "bytecode.h" +#include "value.h" +#include + +int main() { + Bytecode *bc = bytecode_new(); + + // Example: test OP_ADD + int c1 = bytecode_add_constant(bc, make_int(5)); + int c2 = bytecode_add_constant(bc, make_int(3)); + bytecode_add_instruction(bc, OP_CONSTANT, c1); + bytecode_add_instruction(bc, OP_CONSTANT, c2); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + VM vm; + vm_init(&vm); + + printf("=== Bytecode dump ===\n"); + bytecode_dump(bc); + + if (vm_run(&vm, bc) != 0) { + printf("Runtime error!\n"); + } + + printf("Output count: %d\n", vm.output_count); + for (int i = 0; i < vm.output_count; i++) { + printf("Output[%d] = %ld\n", i, vm.output[i].i); + } + + vm_free(&vm); + bytecode_free(bc); + return 0; +} + diff --git a/src/value.c b/src/value.c new file mode 100644 index 0000000..566dc0f --- /dev/null +++ b/src/value.c @@ -0,0 +1,92 @@ +#include "value.h" +#include +#include +#include + +Value make_int(int64_t v) { + Value val; + val.type = VAL_INT; + val.i = v; + return val; +} + +Value make_string(const char *s) { + Value val; + val.type = VAL_STRING; + if (s) val.s = strdup(s); + else val.s = strdup(""); + return val; +} + +Value make_function(struct Bytecode *fn) { + Value val; + val.type = VAL_FUNCTION; + val.fn = fn; + return val; +} + +Value make_nil(void) { + Value v; + v.type = VAL_NIL; + return v; +} + +Value copy_value(const Value *v) { + Value out; + out.type = v->type; + switch (v->type) { + case VAL_INT: + out.i = v->i; + break; + case VAL_STRING: + out.s = v->s ? strdup(v->s) : strdup(""); + break; + case VAL_FUNCTION: + out.fn = v->fn; /* shallow copy pointer */ + break; + case VAL_NIL: + default: + break; + } + return out; +} + +void free_value(Value v) { + if (v.type == VAL_STRING && v.s) { + free(v.s); + } + /* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */ +} + +void print_value(const Value *v) { + switch (v->type) { + case VAL_INT: + printf("%" PRId64, v->i); + break; + case VAL_STRING: + printf("%s", v->s ? v->s : ""); + break; + case VAL_FUNCTION: + printf("", (void*)v->fn); + break; + case VAL_NIL: + default: + printf("nil"); + break; + } +} + +int value_is_truthy(const Value *v) { + switch (v->type) { + case VAL_INT: + return v->i != 0; + case VAL_STRING: + return v->s && v->s[0] != '\0'; + case VAL_FUNCTION: + return 1; + case VAL_NIL: + default: + return 0; + } +} + diff --git a/src/value.h b/src/value.h new file mode 100644 index 0000000..d13f7ea --- /dev/null +++ b/src/value.h @@ -0,0 +1,39 @@ +#ifndef FUN_VALUE_H +#define FUN_VALUE_H + +#include + +struct Bytecode; /* forward */ + +typedef enum { + VAL_INT, + VAL_STRING, + VAL_FUNCTION, + VAL_NIL +} ValueType; + +typedef struct { + ValueType type; + union { + int64_t i; + char *s; + struct Bytecode *fn; + }; +} Value; + +/* constructors / helpers */ +Value make_int(int64_t v); +Value make_string(const char *s); +Value make_function(struct Bytecode *fn); +Value make_nil(void); + +/* copy (deep for strings), free (free string only) */ +Value copy_value(const Value *v); +void free_value(Value v); + +/* utilities */ +void print_value(const Value *v); +int value_is_truthy(const Value *v); + +#endif + diff --git a/src/vm.c b/src/vm.c new file mode 100644 index 0000000..9e34ecf --- /dev/null +++ b/src/vm.c @@ -0,0 +1,453 @@ +#define _GNU_SOURCE +#include "vm.h" +#include "value.h" +#include +#include + +void vm_clear_output(VM *vm) { + for (int i = 0; i < vm->output_count; ++i) { + free_value(vm->output[i]); + } + vm->output_count = 0; +} + +void vm_free(VM *vm) { + //if (vm->output) free(vm->output); + //vm->output = NULL; + //vm->output_count = 0; +} + +static void push_value(VM *vm, Value v) { + if (vm->sp >= VM_STACK_SIZE - 1) { + fprintf(stderr, "Runtime error: stack overflow\n"); + exit(1); + } + vm->stack[++vm->sp] = v; /* take ownership of v */ +} + +static Value pop_value(VM *vm) { + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow\n"); + exit(1); + } + return vm->stack[vm->sp--]; /* caller owns returned Value */ +} + +static void frame_init(Frame *f) { + f->fn = NULL; + f->ip = 0; + for (int i = 0; i < FRAME_MAX_LOCALS; ++i) f->locals[i] = make_nil(); +} + +void vm_init(VM *vm) { + vm->sp = -1; + vm->fp = -1; + vm->output_count = 0; + for (int i = 0; i < VM_MAX_GLOBALS; ++i) + vm->globals[i] = make_nil(); +} + +/* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */ +static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) { + if (vm->fp >= VM_MAX_FRAMES - 1) { + fprintf(stderr, "Runtime error: too many frames\n"); + exit(1); + } + Frame *f = &vm->frames[++vm->fp]; + frame_init(f); + f->fn = fn; + f->ip = 0; + /* move args into locals 0..argc-1 */ + for (int i = 0; i < argc && i < FRAME_MAX_LOCALS; ++i) { + f->locals[i] = args[i]; /* transfer ownership */ + } +} + +/* pop current frame and free its locals */ +static void vm_pop_frame(VM *vm) { + if (vm->fp < 0) { + fprintf(stderr, "Runtime error: pop frame with empty frame stack\n"); + exit(1); + } + Frame *f = &vm->frames[vm->fp]; + for (int i = 0; i < FRAME_MAX_LOCALS; ++i) { + free_value(f->locals[i]); + f->locals[i] = make_nil(); + } + vm->fp--; +} + +void vm_print_output(VM *vm) { + for (int i = 0; i < vm->output_count; ++i) { + print_value(&vm->output[i]); + printf("\n"); + } +} + +void vm_run(VM *vm, Bytecode *entry) { + /* start with entry frame (no args) */ + vm_push_frame(vm, entry, 0, NULL); + + while (vm->fp >= 0) { + Frame *f = &vm->frames[vm->fp]; + + if (f->ip < 0 || f->ip >= f->fn->instr_count) { + /* no more instructions in this frame -> implicit return nil */ + Value nilv = make_nil(); + vm_pop_frame(vm); + push_value(vm, nilv); + continue; + } + + Instruction inst = f->fn->instructions[f->ip++]; + switch (inst.op) { + case OP_NOP: + break; + + case OP_LOAD_CONST: { + int idx = inst.operand; + if (idx < 0 || idx >= f->fn->const_count) { + fprintf(stderr, "Runtime error: constant index out of range\n"); + exit(1); + } + Value c = copy_value(&f->fn->constants[idx]); + push_value(vm, c); + break; + } + + case OP_LOAD_LOCAL: { + int slot = inst.operand; + if (slot < 0 || slot >= FRAME_MAX_LOCALS) { + fprintf(stderr, "Runtime error: local slot out of range\n"); + exit(1); + } + Value val = copy_value(&f->locals[slot]); + push_value(vm, val); + break; + } + + case OP_STORE_LOCAL: { + int slot = inst.operand; + if (slot < 0 || slot >= FRAME_MAX_LOCALS) { + fprintf(stderr, "Runtime error: local slot out of range\n"); + exit(1); + } + Value v = pop_value(vm); + /* free previous local then move v into it */ + free_value(f->locals[slot]); + f->locals[slot] = v; + break; + } + + case OP_ADD: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: ADD expects ints\n"); + exit(1); + } + Value res = make_int(a.i + b.i); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_SUB: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: SUB expects ints\n"); + exit(1); + } + Value res = make_int(a.i - b.i); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_MUL: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: MUL expects ints\n"); + exit(1); + } + Value res = make_int(a.i * b.i); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_DIV: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: DIV expects ints\n"); + exit(1); + } + if (b.i == 0) { + fprintf(stderr, "Runtime error: division by zero\n"); + exit(1); + } + Value res = make_int(a.i / b.i); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_LT: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: LT expects ints\n"); + exit(1); + } + Value res = make_int(a.i < b.i ? 1 : 0); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_LTE: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: LTE expects ints\n"); + exit(1); + } + Value res = make_int(a.i <= b.i ? 1 : 0); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_JUMP: { + f->ip = inst.operand; + break; + } + + case OP_GT: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: GT expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i > b.i ? 1 : 0)); + free_value(a); free_value(b); + break; + } + + case OP_GTE: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: GTE expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i >= b.i ? 1 : 0)); + free_value(a); free_value(b); + break; + } + + case OP_EQ: { + Value b = pop_value(vm); + Value a = pop_value(vm); + push_value(vm, make_int((a.type == b.type && a.i == b.i) ? 1 : 0)); // for ints only, extend later + free_value(a); free_value(b); + break; + } + + case OP_NEQ: { + Value b = pop_value(vm); + Value a = pop_value(vm); + push_value(vm, make_int((a.type != b.type || a.i != b.i) ? 1 : 0)); // for ints only + free_value(a); free_value(b); + break; + } + + case OP_JUMP_IF_FALSE: { + Value cond = pop_value(vm); + int truthy = value_is_truthy(&cond); + free_value(cond); + if (!truthy) { + f->ip = inst.operand; + } + break; + } + + case OP_CALL: { + int argc = inst.operand; + if (argc < 0) argc = 0; + /* collect args in reverse (preserve order) */ + Value *args = NULL; + if (argc > 0) { + args = (Value*)malloc(sizeof(Value) * argc); + /* pop args into array in reverse */ + for (int i = argc - 1; i >= 0; --i) { + args[i] = pop_value(vm); + } + } + /* pop function value */ + Value fnv = pop_value(vm); + if (fnv.type != VAL_FUNCTION) { + fprintf(stderr, "Runtime type error: CALL expects function\n"); + exit(1); + } + /* push new frame and transfer args */ + vm_push_frame(vm, fnv.fn, argc, args); + /* free args array (locals moved), free fnv (no-op for function) */ + free(args); + /* note: fnv contains a pointer to the Bytecode, don't free here */ + break; + } + + case OP_RETURN: { + Value retv; + /* if there's something on the stack -> return it, else nil */ + if (vm->sp >= 0) retv = pop_value(vm); + else retv = make_nil(); + + /* pop frame (free locals) */ + vm_pop_frame(vm); + + /* push return value into caller frame (or onto stack if no caller) */ + push_value(vm, retv); + break; + } + + case OP_PRINT: { + Value v = pop_value(vm); + if (vm->output_count < VM_OUTPUT_SIZE) { + vm->output[vm->output_count++] = v; // store instead of printing + } else { + free_value(v); // prevent leak + fprintf(stderr, "Runtime error: output buffer overflow\n"); + exit(1); + } + break; + } + + case OP_HALT: + return; + + case OP_POP: { + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow for POP\n"); + exit(1); + } + Value v = pop_value(vm); + free_value(v); // free the popped value + break; + } + + case OP_DUP: { + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow for DUP\n"); + exit(1); + } + Value top = vm->stack[vm->sp]; + push_value(vm, copy_value(&top)); + break; + } + + case OP_SWAP: { + if (vm->sp < 1) { + fprintf(stderr, "Runtime error: stack underflow for SWAP\n"); + exit(1); + } + Value a = vm->stack[vm->sp]; + Value b = vm->stack[vm->sp - 1]; + vm->stack[vm->sp] = b; + vm->stack[vm->sp - 1] = a; + break; + } + + case OP_LOAD_GLOBAL: { + int idx = inst.operand; + if (idx < 0 || idx >= VM_MAX_GLOBALS) { + fprintf(stderr, "Runtime error: global index out of range\n"); + exit(1); + } + push_value(vm, copy_value(&vm->globals[idx])); + break; + } + + case OP_STORE_GLOBAL: { + int idx = inst.operand; + if (idx < 0 || idx >= VM_MAX_GLOBALS) { + fprintf(stderr, "Runtime error: global index out of range\n"); + exit(1); + } + Value v = pop_value(vm); + free_value(vm->globals[idx]); + vm->globals[idx] = v; + break; + } + + case OP_MOD: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: MOD expects ints\n"); + exit(1); + } + if (b.i == 0) { + fprintf(stderr, "Runtime error: modulo by zero\n"); + exit(1); + } + Value res = make_int(a.i % b.i); + free_value(a); + free_value(b); + push_value(vm, res); + break; + } + + case OP_AND: { + Value b = pop_value(vm); + Value a = pop_value(vm); + int res = value_is_truthy(&a) && value_is_truthy(&b); + free_value(a); + free_value(b); + push_value(vm, make_int(res)); + break; + } + + case OP_OR: { + Value b = pop_value(vm); + Value a = pop_value(vm); + int res = value_is_truthy(&a) || value_is_truthy(&b); + free_value(a); + free_value(b); + push_value(vm, make_int(res)); + break; + } + + case OP_NOT: { + Value v = pop_value(vm); + int res = !value_is_truthy(&v); + free_value(v); + push_value(vm, make_int(res)); + break; + } + + default: + if (!opcode_is_valid(inst.op)) { + fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n", + inst.op, + (inst.op >= 0 && inst.op < sizeof(opcode_names)/sizeof(opcode_names[0])) + ? opcode_names[inst.op] : "???", + f->ip - 1); + exit(1); + } + break; + } + } +} + diff --git a/src/vm.h b/src/vm.h new file mode 100644 index 0000000..851aa0a --- /dev/null +++ b/src/vm.h @@ -0,0 +1,55 @@ +#ifndef FUN_VM_H +#define FUN_VM_H + +#include "bytecode.h" + +#define VM_STACK_SIZE 1024 +#define VM_MAX_FRAMES 128 +#define VM_MAX_GLOBALS 128 +#define VM_OUTPUT_SIZE 1024 +#define FRAME_MAX_LOCALS 64 + +static const char *opcode_names[] = { + "NOP","LOAD_CONST","LOAD_LOCAL","STORE_LOCAL", + "LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV", + "LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP", + "JUMP_IF_FALSE","CALL","RETURN","PRINT","HALT", + "MOD","AND","OR","NOT","DUP","SWAP" +}; + +typedef struct { + Bytecode *fn; + int ip; + Value locals[FRAME_MAX_LOCALS]; +} Frame; + +typedef struct { + Value stack[VM_STACK_SIZE]; + int sp; + + Frame frames[VM_MAX_FRAMES]; + int fp; // frame pointer, -1 when no frame + + Value globals[VM_MAX_GLOBALS]; + + Value output[VM_OUTPUT_SIZE]; // store printed values + int output_count; +} VM; + +// initialize VM (zero state) +void vm_init(VM *vm); + +// run entry Bytecode (pushes first frame) +void vm_run(VM *vm, Bytecode *entry); + +// helper function to clear the output +void vm_clear_output(VM *vm); + +void vm_free(VM *vm); + +static inline int opcode_is_valid(int op) { + return op >= OP_NOP && op <= OP_SWAP; // all current opcodes +} + +#endif +