diff --git a/dub.sdl b/dub.sdl index b3d0a67..2c95751 100644 --- a/dub.sdl +++ b/dub.sdl @@ -18,8 +18,7 @@ configuration "clox-dbg" { sourcePaths "src/clox" buildRequirements "requireBoundsCheck" "requireContracts" - debugVersions "printCode" - debugVersions "traceExec" + debugVersions "printCode" "traceExec" /* debugVersions "stressGC" */ /* debugVersions "disableGC" */ diff --git a/src/clox/chunk.d b/src/clox/chunk.d index b03c5e3..4eb7cb5 100644 --- a/src/clox/chunk.d +++ b/src/clox/chunk.d @@ -14,6 +14,7 @@ enum OpConst; enum OpStack; enum OpJump; enum OpCall; +enum OpInvoke; enum OpCode : ubyte{ @OpConst @(OpColour("200", "200", "100")) Constant, @@ -56,13 +57,16 @@ enum OpCode : ubyte{ @(OpColour("000", "200", "100")) Print, @OpCall @(OpColour("250", "200", "250")) Call, - @(OpColour("250", "200", "250")) Invoke, + @OpInvoke @(OpColour("250", "200", "250")) Invoke, @(OpColour("250", "200", "250")) Closure, @(OpColour("250", "200", "050")) CloseUpvalue, @(OpColour("250", "190", "200")) Return, @OpConst @(OpColour("050", "190", "200")) Class, @OpConst @(OpColour("100", "190", "200")) Method, + @OpConst @(OpColour("050", "100", "200")) GetSuper, + @OpInvoke @(OpColour("050", "100", "200")) InvokeSuper, + @(OpColour("050", "100", "200")) Inherit, } struct Chunk{ diff --git a/src/clox/compiler.d b/src/clox/compiler.d index 4c56209..a877e88 100644 --- a/src/clox/compiler.d +++ b/src/clox/compiler.d @@ -103,6 +103,7 @@ struct Compiler{ struct ClassCompiler{ ClassCompiler* enclosing; + bool hasSuperclass; } void markCompilerRoots(){ diff --git a/src/clox/container/table.d b/src/clox/container/table.d index 6cca1b0..c989dcb 100644 --- a/src/clox/container/table.d +++ b/src/clox/container/table.d @@ -115,6 +115,12 @@ struct Table(K, V){ entry.remove(); alive--; } + void addAll(typeof(this) tbl){ + foreach(entry; tbl.pool){ + if(entry.isAlive) + this[entry.key] = entry.val; + } + } } void removeWhite(ref Table!(char[], Obj.String*) table){ diff --git a/src/clox/dbg.d b/src/clox/dbg.d index fdb1ae2..2eaa52e 100644 --- a/src/clox/dbg.d +++ b/src/clox/dbg.d @@ -70,14 +70,14 @@ private long closureInstruction(Chunk* chunk, long offset){ printf(" "); return offset ; } -private long invokeInstruction(Chunk* chunk, long offset){ +private long invokeInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){ enum c = getUDAs!(OpCode.Invoke, OpColour)[0]; ubyte len; int constant = VarUint.read(&chunk.code[offset + 1], len); offset += len; int argCount = VarUint.read(&chunk.code[offset + 1], len); offset += len; - printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Pink)).ptr, "Invoke".ptr, constant); + printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Pink)).ptr, name, constant); long pl = chunk.constants[constant].print(); foreach(i; 0 .. 16-pl) printf(" "); @@ -122,8 +122,8 @@ long disassembleInstruction(Chunk* chunk, long offset){ case e: return callInstruction!e(e.stringof, chunk, offset); } else static if(e == OpCode.Closure){ case e: return closureInstruction(chunk, offset); - } else static if(e == OpCode.Invoke){ - case e: return invokeInstruction(chunk, offset); + } else static if(hasUDA!(e, OpInvoke)){ + case e: return invokeInstruction!e(e.stringof, chunk, offset); } else { case e: return simpleInstruction!e(e.stringof, offset); } diff --git a/src/clox/parser.d b/src/clox/parser.d index 7c81e87..d9578bd 100644 --- a/src/clox/parser.d +++ b/src/clox/parser.d @@ -84,6 +84,21 @@ struct Parser{ scope(exit) currentClass = currentClass.enclosing; + if(match(Token.Type.Less)){ + consume(Token.Type.Identifier, "Expect superclass name."); + variable(currentCompiler, false); + if(className.lexeme == previous.lexeme) + error("A class can't inherit from itself."); + + beginScope(); + addLocal(Token.synth("super")); + defineVariable(0); + + namedVariable(className, false); + currentCompiler.emit(OpCode.Inherit); + classCompiler.hasSuperclass = true; + } + namedVariable(className, false); consume(Token.Type.LeftBrace, "Expect '{' before class body."); while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF)){ @@ -91,6 +106,8 @@ struct Parser{ } consume(Token.Type.RightBrace, "Expect '}' after class body."); currentCompiler.emit(OpCode.Pop); + if(classCompiler.hasSuperclass) + endScope(); } void method(){ @@ -311,7 +328,7 @@ struct Parser{ Value nameVal = Value.from(str); return currentCompiler.makeConstant(nameVal); } - void namedVariable(in ref Token name, bool canAssign){ + void namedVariable(in Token name, bool canAssign){ OpCode getOp, setOp; int arg = resolveLocal(currentCompiler, name); if(arg != -1){ @@ -365,7 +382,7 @@ struct Parser{ compiler.upvalues ~= Upvalue(index: index, isLocal: isLocal); return compiler.func.upvalueCount++; } - void addLocal(in ref Token name){ + void addLocal(in Token name){ currentCompiler.locals ~= Compiler.Local(name, -1, false); } void beginScope(){ diff --git a/src/clox/parserules.d b/src/clox/parserules.d index 63ca680..2050450 100644 --- a/src/clox/parserules.d +++ b/src/clox/parserules.d @@ -67,7 +67,7 @@ private void strlit(Compiler* compiler, bool _){ Obj.String* strObj = Obj.String.copy(str); compiler.emitConstant(Value.from(strObj)); } -private void variable(Compiler* compiler, bool canAssign){ +void variable(Compiler* compiler, bool canAssign){ parser.namedVariable(parser.previous, canAssign); } private void and(Compiler* compiler, bool _){ @@ -113,6 +113,24 @@ private void this_(Compiler* compiler, bool _){ } variable(compiler, false); } +private void super_(Compiler* compiler, bool _){ + if(currentClass is null) + parser.error("Can't use 'super' outside of a class."); + else if(!currentClass.hasSuperclass) + parser.error("Can't use 'super' in a class with no superclass."); + parser.consume(Token.Type.Dot, "Expect '.' after 'super'."); + parser.consume(Token.Type.Identifier, "Expect superclass method name."); + auto name = VarUint(parser.identifierConstant(parser.previous)); + parser.namedVariable(Token.synth("this"), false); + if(parser.match(Token.Type.LeftParen)){ + auto argCount = VarUint(parser.argumentList()); + parser.namedVariable(Token.synth("super"), false); + compiler.emit(OpCode.InvokeSuper, name.bytes, argCount.bytes); + } else { + parser.namedVariable(Token.synth("super"), false); + compiler.emit(OpCode.GetSuper, name.bytes); + } +} struct ParseRule{ @@ -172,7 +190,7 @@ immutable ParseRule[Token.Type.max+1] rules = [ Token.Type.Or : ParseRule(null, &or, Precedence.Or), Token.Type.Print : ParseRule(null, null, Precedence.None), Token.Type.Return : ParseRule(null, null, Precedence.None), - Token.Type.Super : ParseRule(null, null, Precedence.None), + Token.Type.Super : ParseRule(&super_, null, Precedence.None), Token.Type.This : ParseRule(&this_, null, Precedence.None), Token.Type.True : ParseRule(&literal, null, Precedence.None), Token.Type.Var : ParseRule(null, null, Precedence.None), diff --git a/src/clox/scanner.d b/src/clox/scanner.d index e81cb43..fcbd31d 100644 --- a/src/clox/scanner.d +++ b/src/clox/scanner.d @@ -20,6 +20,7 @@ struct Token { Type type; string lexeme; int line; + static Token synth(string s) => Token(Token.Type.None, s, 0); } Scanner scanner; diff --git a/src/clox/vm.d b/src/clox/vm.d index b00bb19..10122da 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -299,21 +299,21 @@ struct VM{ case OpCode.Return: Value result = pop(); closeUpvalues(frame.slots); - vm.frameCount--; - if(vm.frameCount == 0){ + frameCount--; + if(frameCount == 0){ pop(); return InterpretResult.Ok; } stackTop = frame.slots; push(result); - frame = &vm.frames[vm.frameCount - 1]; + frame = &frames[frameCount - 1]; break; case OpCode.Invoke: Obj.String* method = readString(); int argCount = readVarUint(); if(!invoke(method, argCount)) return InterpretResult.RuntimeError; - frame = &vm.frames[vm.frameCount - 1]; + frame = &frames[frameCount - 1]; break; case OpCode.Class: push(Value.from(Obj.Class.create(readString()))); @@ -321,6 +321,30 @@ struct VM{ case OpCode.Method: defineMethod(readString()); break; + case OpCode.InvokeSuper: + Obj.String* method = readString(); + int argCount = readVarUint(); + Obj.Class* superclass = pop().asObj.asClass; + if(!invokeFromClass(superclass, method, argCount)) + return InterpretResult.RuntimeError; + frame = &frames[frameCount - 1]; + break; + case OpCode.GetSuper: + Obj.String* name = readString(); + Obj.Class* superclass = pop().asObj.asClass; + if(!bindMethod(superclass, name)) + return InterpretResult.RuntimeError; + break; + case OpCode.Inherit: + Value superclass = peek(1); + if(!superclass.isType(Obj.Type.Class)){ + runtimeError("Superclass must be a class."); + return InterpretResult.RuntimeError; + } + Obj.Class* subclass = peek(0).asObj.asClass; + subclass.methods.addAll(superclass.asObj.asClass.methods); + pop(); // Subclass + break; } } assert(0); diff --git a/test/all.d b/test/all.d index b9b740c..a85a1fa 100755 --- a/test/all.d +++ b/test/all.d @@ -30,15 +30,15 @@ int main(){ "./test/perverseclosure.lox".match("return from outer create inner closure value ".replace(" ", "\n")); "./test/class.lox".match("The German chocolate cake is delicious!\n"); "./test/methods.lox".match("Enjoy your cup of coffee and chicory\nnot a method\n"); - /* "./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n"); */ + "./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n"); "./test/err/invalid_syntax.lox".shouldFail(RetVal.other); "./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name"); "./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable"); "./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable"); "./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code"); - /* "./test/err/super_outside_class.lox".shouldFail(RetVal.other, "Can't use 'super' outside of a class"); */ - /* "./test/err/super_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass"); */ + "./test/err/super_outside_class.lox".shouldFail(RetVal.other, "Can't use 'super' outside of a class"); + "./test/err/super_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass"); return failures; }