import std.stdio; import std.string; import std.conv; import chunk; import parser; import compiler; //import value; //import obj; import dbg; enum InterpretResult { OK, COMPILE_ERROR, RUNTIME_ERROR, } struct CallFrame { Function func; ubyte ip; Value[] slots; int frameStart; } class VM { //int topOffset = 0; /* ubyte ip = 0; */ CallFrame[] frames; CallFrame* current; int frameCount; Value[] aStack; int aTop; Value[] bStack; int bTop; //ObjFunction func; //Function func; Value[string] globals; /* this(Function func) { this.func = func; } */ this() { this.aTop = 0; this.bTop = 0; this.frameCount = 0; } void pushFrame(CallFrame frame) { if (this.frameCount < frames.length) { this.frames[this.frameCount] = frame; } else { this.frames ~= frame; } current = &frames[this.frameCount]; frameCount++; } Value peekA(int offset) { if (offset >= aTop) { writefln("offset of %d greater than stack size %d", offset, aTop); } return this.aStack[aTop - offset - 1]; } Value popA() { if (aTop > 0) { aTop--; } else { writeln("popA() on an empty stack!!"); } return aStack[aTop]; } //InterpretResult push(Value value) { void pushA(Value value) { if (aStack.length > aTop) { aStack[aTop] = value; } else { aStack ~= value; } aTop++; } Value popB() { if (bTop > 0) { bTop--; //current.frameStart--; } else { writeln("popB() on an empty stack!!"); } //return bStack[bTop]; return current.slots[bTop - current.frameStart - 1]; } void pushB(Value value) { /* writefln("length %d, bTop %d", current.slots.length, bTop); if (current.slots.length > bTop) { current.slots[bTop] = value; } else { current.slots ~= value; } bTop++; current.frameStart++; */ if (bStack.length > bTop) { bStack[bTop] = value; } else { bStack ~= value; } bTop++; } ubyte readByte() { return current.func.chunk.code[current.ip++]; } uint readShort() { current.ip += 2; uint high = current.func.chunk.code[current.ip - 2] << 8; uint low = current.func.chunk.code[current.ip - 1]; return high | low; //return 0; } bool isNumber(Value value) { return value.type == ValueType.NUMBER; } bool isString(Value value) { return value.type == ValueType.STRING; } bool isBoolean(Value value) { return value.type == ValueType.BOOLEAN; } bool isObj(Value value) { return value.type == ValueType.OBJ; } ObjType objTypeOf(Value value) { return value.as.obj.type; } double asNumber(Value value) { return value.as.number; } string asString(Value value) { return value.as.str; } bool asBoolean(Value value) { return value.as.boolean; } Function asFunction(Value value) { return cast(Function)value.as.obj; } /* bool isFalsey(Value val) { return isBoolean(val) && val.as.boolean; } */ /* CallFrame currentFrame() { return frames[frameCount - 1]; } */ bool callValue(Value callee, int argCount) { if (isObj(callee)) { switch (callee.as.obj.type) { case ObjType.FUNCTION: return call(asFunction(callee), argCount); case ObjType.SCRIPT: writeln("trying to call the script?"); break; default: break; } } else { writeln("callee is not an object :("); } writeln("can only call functions!"); return false; } bool call(Function func, int argCount) { CallFrame frame = { func, 0, bStack, bTop - argCount - 1 }; // i need to do sthg with argCount //frames ~= frame; //frameCount++; pushFrame(frame); return true; } InterpretResult run() { writeln("== VM running =="); while (true) { /* writeln(" Stacks:"); write(" A> "); for (int i = 0; i < aTop; i++) { writef("[ %s ]", printableValue(aStack[i])); } write("\n B> "); for (int i = 0; i < bTop; i++) { //writef("[ %s ]", printableValue(bStack[i])); writef("[ %s ]", printableValue(current.slots[i])); } writeln("\n--"); */ /* write(" globals >"); //for (int i = 0; i < this.topOffset; i++) { foreach (Value val; globals) { writef("[ %s ]", printableValue(val)); } writeln("\n--"); */ //disassemble(current.func.chunk, current.ip); ubyte inst; switch (inst = this.readByte()) { case OpCode.OP_DEF_GLOBAL: Value name = current.func.chunk.constants[readByte()]; if (!isString(name)) { writefln("variables must be strings, got %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } Value val = popA(); globals[asString(name)] = val; break; case OpCode.OP_GET_GLOBAL: Value name = current.func.chunk.constants[readByte()]; //writefln(asString(name)); if (!isString(name)) { writefln("variables must be strings, got %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } Value* member = asString(name) in globals; if (member is null) { writefln("no such variable: %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } pushA(*member); // do i need to dereference? break; case OpCode.OP_DEF_LOCAL: Value val = popA(); pushB(val); //current.slots ~= val; break; case OpCode.OP_GET_LOCAL: ubyte slot = readByte(); //pushA(bStack[bTop - slot - 1]); //pushA(current.slots[slot - 1]); //pushA(current.slots[current.frameStart + slot + 1]); pushA(current.slots[current.frameStart + slot]); break; case OpCode.OP_SET_LOCAL: ubyte slot = readByte(); //bStack[bTop + slot - 1] = peekA(0); current.slots[slot] = peekA(0); //bStack[bTop + slot - 1] = peekA(0); popA(); break; case OpCode.OP_CONSTANT: Value constant = current.func.chunk.constants[readByte()]; //Value constant = current.func.chunk.constants[b]; pushA(constant); break; case OpCode.OP_NEGATE: if (!isNumber(peekA(0))) { writeln("negate operand must be a number"); return InterpretResult.RUNTIME_ERROR; } double val = asNumber(popA()); pushA(makeNumberValue(val * -1)); break; case OpCode.OP_ADD: Value b = popA(); if (!isNumber(b)) { writeln("b is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } Value a = popA(); if (!isNumber(a)) { writeln("a is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } double bnum = asNumber(b); double anum = asNumber(a); pushA(makeNumberValue(anum + bnum)); break; case OpCode.OP_SUBTRACT: Value b = popA(); if (!isNumber(b)) { writeln("b is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } Value a = popA(); if (!isNumber(a)) { writeln("a is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } double bnum = asNumber(b); double anum = asNumber(a); pushA(makeNumberValue(anum - bnum)); break; case OpCode.OP_LESS: Value b = popA(); if (!isNumber(b)) { writeln("b is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } Value a = popA(); if (!isNumber(a)) { writeln("a is not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } double bnum = asNumber(b); double anum = asNumber(a); pushA(makeBooleanValue(anum < bnum)); break; case OpCode.OP_RETURN: Value ret = popA(); popA(); // function this.frameCount--; //writefln("frameCount: %d", frameCount); //if (this.frameCount == 0) { if (this.frameCount == 1) { popA(); writefln("returned %s", printableValue(ret)); return InterpretResult.OK; } // do something with the stack top/frame slots?? while(bTop > current.frameStart + 1) { popB(); } pushA(ret); current = &frames[this.frameCount -1]; break; case OpCode.OP_POP: popA(); break; case OpCode.OP_POPB: popB(); break; case OpCode.OP_POP_SCOPE: // pop the n-1 position Value val = popA(); popA(); // throw this one away pushA(val); break; case OpCode.OP_JUMP: uint offset = readShort(); //ip += offset; current.ip += offset; break; case OpCode.OP_JUMP_IF_FALSE: uint offset = readShort(); if (!isBoolean(peekA(0))) { writeln("expecting a boolean condition"); return InterpretResult.RUNTIME_ERROR; // TODO error } if (!asBoolean(peekA(0))) { //ip += offset; current.ip += offset; } break; case OpCode.OP_JUMP_IF_TRUE: uint offset = readShort(); if (!isBoolean(peekA(0))) { writeln("expecting a boolean condition"); return InterpretResult.RUNTIME_ERROR; // TODO error } if (asBoolean(peekA(0))) { current.ip += offset; } break; case OpCode.OP_CALL: ubyte argCount = readByte(); // TODO i think i need to move the arguments from stack A to stack B (preserving order) int cnt = to!int(argCount) - 1; Value[] tmp; while (cnt >= 0) { tmp ~= popA(); cnt--; } foreach (Value val ; tmp) { pushB(val); } //if (!callValue(peekA(argCount), argCount)) { // i'm allocating variables wrong if (!callValue(peekA(0), argCount)) { // i'm allocating variables wrong return InterpretResult.RUNTIME_ERROR; } current = &frames[this.frameCount - 1]; break; default: writeln("unknown opcode to run"); break; } } } } InterpretResult interpret(string source) { Parser parser = new Parser(source); Compiler compiler = new Compiler(ObjType.SCRIPT, &parser); Function func = compiler.compile(); VM vm = new VM(); vm.pushA(makeObjValue(func)); CallFrame frame = { func, 0, vm.bStack, 0 }; vm.pushFrame(frame); vm.call(func, 0); //InterpretResult result = run(); return vm.run(); }