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 slotStart; 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 < this.frames.length) { this.frames[this.frameCount] = frame; } else { this.frames ~= frame; } this.current = &this.frames[this.frameCount]; this.frameCount++; } Value peekA(int offset) { if (offset >= this.aTop) { writefln("peekA() offset of %d greater than stack size %d", offset, this.aTop); } return this.aStack[this.aTop - offset - 1]; } Value popA() { if (this.aTop > 0) { this.aTop--; } else { writeln("popA() on an empty stack!!"); } return this.aStack[this.aTop]; } void pushA(Value value) { if (this.aStack.length > this.aTop) { this.aStack[this.aTop] = value; } else { this.aStack ~= value; } this.aTop++; } Value peekB(int offset) { if (offset >= this.bTop) { writefln("peekB() offset of %d greater than stack size %d", offset, this.bTop); } //return this.bStack[this.bTop]; //return this.bStack[this.bTop - offset]; return this.bStack[this.bTop - offset - 1]; } Value popB() { if (this.bTop > 0) { this.bTop--; //current.frameStart--; } else { writeln("popB() on an empty stack!!"); } //return bStack[bTop]; //return this.current.slots[this.bTop - this.current.frameStart - 1]; //return this.bStack[this.bTop - this.current.frameStart - 1]; return this.bStack[this.bTop]; } 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 (this.bStack.length > this.bTop) { this.bStack[this.bTop] = value; } else { this.bStack ~= value; } this.bTop++; } ubyte readByte() { return this.current.func.chunk.code[this.current.ip++]; } uint readShort() { this.current.ip += 2; uint high = this.current.func.chunk.code[this.current.ip - 2] << 8; uint low = this.current.func.chunk.code[this.current.ip - 1]; return high | low; } Seq nil() { List nil = new List(0); return nil; } bool isNumber(Value value) { return value.type == ValueType.NUMBER; } bool isString(Value value) { return value.type == ValueType.STRING || value.type == ValueType.TYPE || value.type == ValueType.SEQ; // WRONG } bool isNebString(Value value) { if (value.type != ValueType.SEQ) { return false; } return value.as.seq.type == SeqType.STRING; } bool isType(Value value) { return value.type == ValueType.TYPE; } bool isBoolean(Value value) { return value.type == ValueType.BOOLEAN; } bool isObj(Value value) { return value.type == ValueType.OBJ; } bool isSeq(Value value) { return value.type == ValueType.SEQ; } bool isNil(Value value) { return isList(value) && value.as.seq.length() == 0; } bool isList(Value value) { return isSeq(value) && seqTypeOf(value) == SeqType.LIST; } ObjType objTypeOf(Value value) { return value.as.obj.type; } SeqType seqTypeOf(Value value) { return value.as.seq.type; } Seq asSeq(Value value) { return value.as.seq; } List asList(Value value) { return cast(List)value.as.seq; } String asNebString(Value value) { return cast(String)value.as.seq; } double asNumber(Value value) { return value.as.number; } string asString(Value value) { if (value.type == ValueType.TYPE) { return value.as.type; } else if (value.type == ValueType.SEQ) { String str = cast(String)value.as.seq; return str.str; } else { 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; } */ bool callValue(Value callee, int argCount) { if (isObj(callee)) { switch (callee.as.obj.type) { case ObjType.FUNCTION: return this.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, this.bStack, this.bTop - argCount - 1 }; // i need to do sthg with argCount //CallFrame frame = { func, 0, 0, this.bTop - argCount - 1 }; // i need to do sthg with argCount CallFrame frame = { func, 0, 0, this.bTop - argCount - 1 }; // i need to do sthg with argCount //frames ~= frame; //frameCount++; this.pushFrame(frame); return true; } InterpretResult run() { if (DEBUG) { writeln("== VM running =="); } while (true) { if (DEBUG) { writefln(" atop: %d, btop: %d", this.aTop, this.bTop); writeln(" Stacks:"); write(" A> "); for (int i = 0; i < this.aTop; i++) { writef("[ %s ]", printableValue(this.aStack[i])); } write("\n B> "); for (int i = 0; i < this.bTop; i++) { writef("[ %s ]", printableValue(bStack[i])); } write("\n constants> "); for (int i = 0; i < this.current.func.chunk.constants.length; i++) { writef("[ %s ]", printableValue(this.current.func.chunk.constants[i])); } writeln("\n--"); disassemble(this.current.func.chunk, this.current.ip); } /* write(" globals >"); //for (int i = 0; i < this.topOffset; i++) { foreach (Value val; globals) { writef("[ %s ]", printableValue(val)); } writeln("\n--"); */ ubyte inst; switch (inst = this.readByte()) { case OpCode.OP_IS_NIL: Value val = this.popA(); this.pushA(makeBooleanValue(isNil(val))); break; case OpCode.OP_DUPLICATE: this.pushA(this.peekA(0)); break; case OpCode.OP_DUPLICATE_2: this.pushA(this.peekA(1)); this.pushA(this.peekA(1)); break; case OpCode.OP_ROTATE_N: int n = to!int(this.readByte()); // pop the top of the stack Value top = this.popA(); // pop the next n-1 values Value[] inner; for (int i = 0; i < (n - 1); i++) { inner ~= this.popA(); } // push the top this.pushA(top); // push the other values (preserve order) for (int i = (n - 2); i >= 0; i--) { this.pushA(inner[i]); } break; case OpCode.OP_ZERO: this.pushA(makeNumberValue(0)); break; case OpCode.OP_INCREMENT: Value val = this.popA(); double num = asNumber(val); this.pushA(makeNumberValue(num + 1)); break; case OpCode.OP_DEF_GLOBAL: Value name = this.current.func.chunk.constants[this.readByte()]; if (!isString(name)) { writefln("variables must be strings, got %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } Value val = this.popA(); this.globals[asString(name)] = val; /* Value nil = { ValueType.NIL }; this.pushA(nil); */ break; case OpCode.OP_GET_GLOBAL: Value name = this.current.func.chunk.constants[this.readByte()]; if (!isString(name)) { writefln("variables must be strings, got %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } Value* member = asString(name) in this.globals; if (member is null) { writefln("no such variable: %s", printableValue(name)); return InterpretResult.RUNTIME_ERROR; // TODO error } this.pushA(*member); // do i need to dereference? break; case OpCode.OP_DEF_LOCAL: Value val = this.popA(); this.pushB(val); /* Value nil = { ValueType.NIL }; this.pushA(nil); */ break; case OpCode.OP_GET_LOCAL: ubyte slot = this.readByte(); //pushA(bStack[bTop - slot - 1]); //pushA(current.slots[slot - 1]); //pushA(current.slots[current.frameStart + slot + 1]); //pushA(this.current.slots[this.current.frameStart + slot]); pushA(this.peekB(this.bTop - (this.current.frameStart + slot) - 1)); break; case OpCode.OP_SET_LOCAL: ubyte slot = this.readByte(); //this.current.slots[slot] = this.peekA(0); this.pushB(this.peekA(0)); //bStack[bTop + slot - 1] = peekA(0); this.popA(); break; case OpCode.OP_LIST_N: Value lengthVal = this.popA(); int length = to!int(asNumber(lengthVal)); List lst = new List(length); for (int i = length - 1; i >= 0; i--) { lst.addItemAtIndex(this.popA(), i); } this.pushA(makeSeqValue(lst)); break; case OpCode.OP_LIST: int length = to!int(this.readByte()); List lst = new List(length); if (length > 0) { for (int i = length - 1; i >= 0; i--) { lst.addItemAtIndex(this.popA(), i); } } this.pushA(makeSeqValue(lst)); break; case OpCode.OP_CONSTANT: Value constant = this.current.func.chunk.constants[this.readByte()]; this.pushA(constant); break; case OpCode.OP_NEGATE: if (!isNumber(this.peekA(0))) { writeln("negate operand must be a number"); return InterpretResult.RUNTIME_ERROR; } double val = asNumber(this.popA()); this.pushA(makeNumberValue(val * -1)); break; case OpCode.OP_TYPE_CHECK_SEQ: if (!isSeq(this.peekA(0))) { writeln("VM type check: not a seq!"); return InterpretResult.RUNTIME_ERROR; // TODO error } break; case OpCode.OP_TYPE_CHECK_LIST: if (!isList(this.peekA(0))) { writeln("VM type check: not a list!"); return InterpretResult.RUNTIME_ERROR; // TODO error } break; case OpCode.OP_TYPE_CHECK_STRING: if (!isString(this.peekA(0))) { writeln("VM type check: not a string!"); return InterpretResult.RUNTIME_ERROR; // TODO error } break; case OpCode.OP_TYPE_CHECK_NUMBER: if (!isNumber(this.peekA(0))) { writeln("VM type check: not a number!"); return InterpretResult.RUNTIME_ERROR; // TODO error } break; case OpCode.OP_TYPE_CHECK_BOOLEAN: if (!isBoolean(this.peekA(0))) { writeln("VM type check: not a boolean!"); return InterpretResult.RUNTIME_ERROR; // TODO error } break; case OpCode.OP_FIRST: Seq seq = asSeq(this.popA()); this.pushA(seq.first()); break; case OpCode.OP_REST: Seq seq = asSeq(this.popA()); this.pushA(makeSeqValue(seq.rest())); break; case OpCode.OP_MOST: Seq seq = asSeq(this.popA()); this.pushA(makeSeqValue(seq.most())); break; case OpCode.OP_LAST: Seq seq = asSeq(this.popA()); this.pushA(seq.last()); break; case OpCode.OP_LENGTH: Seq seq = asSeq(this.popA()); this.pushA(makeNumberValue(seq.length())); break; case OpCode.OP_REVERSE: Seq seq = asSeq(this.popA()); this.pushA(makeSeqValue(seq.reverse())); break; case OpCode.OP_MEMBER: Seq seq = asSeq(this.popA()); Value val = this.popA(); this.pushA(makeBooleanValue(seq.isIn(val))); break; case OpCode.OP_ADD: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeNumberValue(anum + bnum)); break; case OpCode.OP_SUBTRACT: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeNumberValue(anum - bnum)); break; case OpCode.OP_MULTIPLY: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeNumberValue(anum * bnum)); break; case OpCode.OP_DIVIDE: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeNumberValue(anum / bnum)); break; case OpCode.OP_NOT: Value val = this.popA(); bool bval = asBoolean(val); this.pushA(makeBooleanValue(!bval)); break; case OpCode.OP_EQUAL: Value b = this.popA(); Value a = this.popA(); this.pushA(makeBooleanValue(areValuesEqual(a, b))); break; case OpCode.OP_CONCAT: String b = asNebString(this.popA()); String a = asNebString(this.popA()); this.pushA(makeSeqValue(a.concat(b))); break; case OpCode.OP_APPEND: Value b = this.popA(); List a = asList(this.popA()); this.pushA(makeSeqValue(a.append(b))); break; case OpCode.OP_GREATER: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeBooleanValue(anum > bnum)); break; case OpCode.OP_LESS: Value b = this.popA(); Value a = this.popA(); double bnum = asNumber(b); double anum = asNumber(a); this.pushA(makeBooleanValue(anum < bnum)); break; case OpCode.OP_RETURN: Value ret = this.popA(); this.popA(); // function this.frameCount--; if (this.frameCount == 1) { if (DEBUG) { writefln("returned %s", printableValue(ret)); } else if (REPL) { writefln("=> %s", printableValue(ret)); } return InterpretResult.OK; } // do something with the stack top/frame slots?? while(bTop > this.current.frameStart + 1) { this.popB(); } this.pushA(ret); this.current = &this.frames[this.frameCount -1]; break; case OpCode.OP_POP: this.popA(); break; case OpCode.OP_POPB: this.popB(); break; case OpCode.OP_POP_SCOPE: // pop the n-1 position Value val = this.popA(); this.popA(); // throw this one away this.pushA(val); break; case OpCode.OP_PRINT: Value val = this.popA(); if (isNebString(val)) { writefln("%s", tr(asNebString(val).str, `\`, "")); } else { writefln("%s", printableValue(val)); } break; case OpCode.OP_JUMP_TO: uint addr = this.readShort(); //this.current.ip -= offset; this.current.ip = to!ubyte(addr); break; case OpCode.OP_JUMP: uint offset = this.readShort(); this.current.ip += offset; break; case OpCode.OP_JUMP_IF_FALSE: uint offset = this.readShort(); if (!isBoolean(this.peekA(0))) { writeln("expecting a boolean condition"); return InterpretResult.RUNTIME_ERROR; // TODO error } if (!asBoolean(this.peekA(0))) { this.current.ip += offset; } break; case OpCode.OP_JUMP_IF_TRUE: uint offset = this.readShort(); /* if (!isBoolean(peekA(0))) { writeln("expecting a boolean condition"); return InterpretResult.RUNTIME_ERROR; // TODO error } */ if (asBoolean(this.peekA(0))) { this.current.ip += offset; } break; case OpCode.OP_CALL: ubyte argCount = this.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 ~= this.popA(); cnt--; } for (int i = to!int(tmp.length) - 1; i >= 0; i--) { this.pushB(tmp[i]); } //if (!callValue(peekA(argCount), argCount)) { // i'm allocating variables wrong if (!this.callValue(this.peekA(0), argCount)) { // i'm allocating variables wrong return InterpretResult.RUNTIME_ERROR; } this.current = &this.frames[this.frameCount - 1]; break; default: writeln("unknown opcode to run"); break; } } } } InterpretResult interpret(string source, VM vm) { Parser parser = new Parser(source); Compiler compiler = new Compiler(ObjType.SCRIPT, &parser); Function func = compiler.compile(); if (compiler.errors.length > 0) { foreach (CompileError err ; compiler.errors) { writefln("error: %s", printableCompilerError(err)); } return InterpretResult.COMPILE_ERROR; } // vm.globals[":int"] = makeTypeValue(":int"); // reset the frame count (why do i need to do this?) vm.frameCount = 0; vm.pushA(makeObjValue(func)); //CallFrame frame = { func, 0, vm.bStack, 0 }; CallFrame frame = { func, 0, 0, 0 }; vm.pushFrame(frame); vm.call(func, 0); //InterpretResult result = run(); return vm.run(); }