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