1
0
Fork 0
forked from fun/fun

Initial code commit.

This commit is contained in:
Johannes Findeisen 2025-09-13 05:09:20 +02:00
commit 6a7add3e56
13 changed files with 891 additions and 0 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
tmp/
test.*

38
Makefile Normal file
View file

@ -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)

43
src/bytecode.c Normal file
View file

@ -0,0 +1,43 @@
#include "bytecode.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
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);
}

70
src/bytecode.h Normal file
View file

@ -0,0 +1,70 @@
#ifndef FUN_BYTECODE_H
#define FUN_BYTECODE_H
#include <stdint.h>
#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

52
src/main.c Normal file
View file

@ -0,0 +1,52 @@
#include "bytecode.h"
#include "value.h"
#include "vm.h"
#include <stdio.h>
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;
}

12
src/opcode_names.c Normal file
View file

@ -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"
};

0
src/parser.c Normal file
View file

0
src/parser.h Normal file
View file

36
src/test_opcodes.c Normal file
View file

@ -0,0 +1,36 @@
#include "vm.h"
#include "bytecode.h"
#include "value.h"
#include <stdio.h>
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;
}

92
src/value.c Normal file
View file

@ -0,0 +1,92 @@
#include "value.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
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("<function@%p>", (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;
}
}

39
src/value.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef FUN_VALUE_H
#define FUN_VALUE_H
#include <inttypes.h>
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

453
src/vm.c Normal file
View file

@ -0,0 +1,453 @@
#define _GNU_SOURCE
#include "vm.h"
#include "value.h"
#include <stdio.h>
#include <stdlib.h>
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;
}
}
}

55
src/vm.h Normal file
View file

@ -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