Superclasses 29
This commit is contained in:
parent
28b0c71be1
commit
51bc1395f8
10 changed files with 88 additions and 18 deletions
3
dub.sdl
3
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" */
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ struct Compiler{
|
|||
|
||||
struct ClassCompiler{
|
||||
ClassCompiler* enclosing;
|
||||
bool hasSuperclass;
|
||||
}
|
||||
|
||||
void markCompilerRoots(){
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue