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); } }