183 lines
4.9 KiB
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);
|
|
}
|
|
}
|
|
|