Initial code commit.
This commit is contained in:
parent
7aa60fe930
commit
6a7add3e56
13 changed files with 891 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
tmp/
|
||||
test.*
|
||||
|
|
|
|||
38
Makefile
Normal file
38
Makefile
Normal 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
43
src/bytecode.c
Normal 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
70
src/bytecode.h
Normal 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
52
src/main.c
Normal 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
12
src/opcode_names.c
Normal 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
0
src/parser.c
Normal file
0
src/parser.h
Normal file
0
src/parser.h
Normal file
36
src/test_opcodes.c
Normal file
36
src/test_opcodes.c
Normal 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
92
src/value.c
Normal 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
39
src/value.h
Normal 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
453
src/vm.c
Normal 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
55
src/vm.h
Normal 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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue