lox-d/src/clox/vm.d

183 lines
4.9 KiB
D

module clox.vm;
import core.stdc.stdio;
import clox.chunk;
import clox.util;
import clox.dbg;
import clox.object;
import clox.value;
import clox.object;
import clox.compiler;
import clox.memory;
import clox.container.stack;
import clox.container.table;
import clox.container.varint;
VM vm;
struct VM{
Chunk* chunk;
ubyte* ip;
Stack!Value stack;
Table globals;
Table strings;
Obj* objects;
bool isREPL;
enum InterpretResult{ Ok, CompileError, RuntimeError }
void initialise(){
stack.initialise();
strings.initialise();
globals.initialise();
}
void free(){
freeObjects();
strings.free();
globals.free();
stack.free();
}
InterpretResult interpret(const char* source){
Chunk cnk;
cnk.initialise();
scope(exit)
cnk.free();
Compiler compiler;
if(!compiler.compile(source, &cnk))
return InterpretResult.CompileError;
this.chunk = &cnk;
this.ip = cnk.code.ptr;
return run();
}
void runtimeError(Args...)(const char* format, Args args){
fprintf(stderr, format, args);
fputs("\n", stderr);
size_t instruction = vm.ip - vm.chunk.code.ptr - 1;
uint line = vm.chunk.lines[instruction];
fprintf(stderr, "[line %d] in script\n", line);
}
InterpretResult run(){
ubyte readByte() => *ip++;
long readVarUint(){
VarUint vi = VarUint.read(ip);
ip += vi.len;
return vi.i;
}
Value readConstant() => chunk.constants[readVarUint()];
Obj.String* readString() => readConstant().asObj.asString;
Value peek(int distance = 0) => stack.top[-1 - distance];
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
bool checkSameType()() => peek(0).type == peek(1).type;
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
if(!check){
runtimeError(checkMsg.ptr);
return 1;
}
auto b = stack.pop().as!pre;
auto a = stack.pop().as!pre;
stack ~= Value.from(mixin("a", op, "b"));
return 0;
}
while(true){
debug(traceExec){
disassembleInstruction(vm.chunk, vm.ip - vm.chunk.code.ptr);
printf(" ");
foreach(slot; stack.live){
printf("");
slot.print();
printf(colour!(", ", Colour.Black).ptr);
}
printf("\n");
}
OpCode instruction;
opSwitch: final switch(instruction = cast(OpCode)readByte()){
case OpCode.Constant:
Value constant = readConstant();
stack ~= constant;
break;
case OpCode.Nil: stack ~= Value.nil; break;
case OpCode.True: stack ~= Value.from(true); break;
case OpCode.False: stack ~= Value.from(false); break;
case OpCode.Pop: stack.pop(); break;
case OpCode.GetLocal:
long slot = readVarUint();
stack ~= stack[slot];
break;
case OpCode.SetLocal:
long slot = readVarUint();
stack[slot] = peek(0);
break;
case OpCode.GetGlobal:
Obj.String* name = readString();
Value value;
if(!globals.get(name, value)){
runtimeError("Undefined variable '%s'.", name.chars.ptr);
return InterpretResult.RuntimeError;
}
stack ~= value;
break;
case OpCode.DefineGlobal:
Obj.String* name = readString();
globals.set(name, peek(0));
stack.pop();
break;
case OpCode.SetGlobal:
Obj.String* name = readString();
if(globals.set(name, peek(0))){
globals.del(name);
runtimeError("Undefined variable '%s'.", name.chars.ptr);
return InterpretResult.RuntimeError;
}
break;
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
case k:
binaryOp!(op, true, null, Value.Type.None);
break opSwitch;
}
static foreach(k, op; [ OpCode.Greater: ">", OpCode.Less: "<", OpCode.GreaterEqual: ">=", OpCode.LessEqual: "<=" ]){
case k:
if(binaryOp!(op, checkSameType, "Operands must be of the same type.", Value.Type.None))
return InterpretResult.RuntimeError;
break opSwitch;
}
static foreach(k, op; [ OpCode.Add: "+", OpCode.Subtract: "-", OpCode.Multiply: "*", OpCode.Divide: "-" ]){
case k:
static if(k == OpCode.Add){
if(checkBinaryType!(Obj.Type.String)){
Obj.String* b = stack.pop().asObj.asString;
Obj.String* a = stack.pop().asObj.asString;
Obj.String* result = Obj.String.concat(a, b);
stack ~= Value.from(result);
break opSwitch;
}
}
if(binaryOp!(op, checkBinaryType!(Value.Type.Number), "Operands must be numbers.", Value.Type.Number)())
return InterpretResult.RuntimeError;
break opSwitch;
}
case OpCode.Not:
stack ~= Value.from(stack.pop().isFalsey);
break;
case OpCode.Negate:
if(!peek(0).isType(Value.Type.Number)){
runtimeError("Operand must be a number.");
return InterpretResult.RuntimeError;
}
stack ~= Value.from(-stack.pop().asNumber);
break;
case OpCode.Print:
stack.pop().print();
printf("\n");
break;
case OpCode.Return:
return InterpretResult.Ok;
}
}
assert(0);
}
}