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"
|
sourcePaths "src/clox"
|
||||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||||
|
|
||||||
debugVersions "printCode"
|
debugVersions "printCode" "traceExec"
|
||||||
debugVersions "traceExec"
|
|
||||||
|
|
||||||
/* debugVersions "stressGC" */
|
/* debugVersions "stressGC" */
|
||||||
/* debugVersions "disableGC" */
|
/* debugVersions "disableGC" */
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ enum OpConst;
|
||||||
enum OpStack;
|
enum OpStack;
|
||||||
enum OpJump;
|
enum OpJump;
|
||||||
enum OpCall;
|
enum OpCall;
|
||||||
|
enum OpInvoke;
|
||||||
|
|
||||||
enum OpCode : ubyte{
|
enum OpCode : ubyte{
|
||||||
@OpConst @(OpColour("200", "200", "100")) Constant,
|
@OpConst @(OpColour("200", "200", "100")) Constant,
|
||||||
|
|
@ -56,13 +57,16 @@ enum OpCode : ubyte{
|
||||||
@(OpColour("000", "200", "100")) Print,
|
@(OpColour("000", "200", "100")) Print,
|
||||||
|
|
||||||
@OpCall @(OpColour("250", "200", "250")) Call,
|
@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", "250")) Closure,
|
||||||
@(OpColour("250", "200", "050")) CloseUpvalue,
|
@(OpColour("250", "200", "050")) CloseUpvalue,
|
||||||
@(OpColour("250", "190", "200")) Return,
|
@(OpColour("250", "190", "200")) Return,
|
||||||
|
|
||||||
@OpConst @(OpColour("050", "190", "200")) Class,
|
@OpConst @(OpColour("050", "190", "200")) Class,
|
||||||
@OpConst @(OpColour("100", "190", "200")) Method,
|
@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{
|
struct Chunk{
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ struct Compiler{
|
||||||
|
|
||||||
struct ClassCompiler{
|
struct ClassCompiler{
|
||||||
ClassCompiler* enclosing;
|
ClassCompiler* enclosing;
|
||||||
|
bool hasSuperclass;
|
||||||
}
|
}
|
||||||
|
|
||||||
void markCompilerRoots(){
|
void markCompilerRoots(){
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,12 @@ struct Table(K, V){
|
||||||
entry.remove();
|
entry.remove();
|
||||||
alive--;
|
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){
|
void removeWhite(ref Table!(char[], Obj.String*) table){
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,14 @@ private long closureInstruction(Chunk* chunk, long offset){
|
||||||
printf(" ");
|
printf(" ");
|
||||||
return offset ;
|
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];
|
enum c = getUDAs!(OpCode.Invoke, OpColour)[0];
|
||||||
ubyte len;
|
ubyte len;
|
||||||
int constant = VarUint.read(&chunk.code[offset + 1], len);
|
int constant = VarUint.read(&chunk.code[offset + 1], len);
|
||||||
offset += len;
|
offset += len;
|
||||||
int argCount = VarUint.read(&chunk.code[offset + 1], len);
|
int argCount = VarUint.read(&chunk.code[offset + 1], len);
|
||||||
offset += 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();
|
long pl = chunk.constants[constant].print();
|
||||||
foreach(i; 0 .. 16-pl)
|
foreach(i; 0 .. 16-pl)
|
||||||
printf(" ");
|
printf(" ");
|
||||||
|
|
@ -122,8 +122,8 @@ long disassembleInstruction(Chunk* chunk, long offset){
|
||||||
case e: return callInstruction!e(e.stringof, chunk, offset);
|
case e: return callInstruction!e(e.stringof, chunk, offset);
|
||||||
} else static if(e == OpCode.Closure){
|
} else static if(e == OpCode.Closure){
|
||||||
case e: return closureInstruction(chunk, offset);
|
case e: return closureInstruction(chunk, offset);
|
||||||
} else static if(e == OpCode.Invoke){
|
} else static if(hasUDA!(e, OpInvoke)){
|
||||||
case e: return invokeInstruction(chunk, offset);
|
case e: return invokeInstruction!e(e.stringof, chunk, offset);
|
||||||
} else {
|
} else {
|
||||||
case e: return simpleInstruction!e(e.stringof, offset);
|
case e: return simpleInstruction!e(e.stringof, offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,21 @@ struct Parser{
|
||||||
scope(exit)
|
scope(exit)
|
||||||
currentClass = currentClass.enclosing;
|
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);
|
namedVariable(className, false);
|
||||||
consume(Token.Type.LeftBrace, "Expect '{' before class body.");
|
consume(Token.Type.LeftBrace, "Expect '{' before class body.");
|
||||||
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF)){
|
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF)){
|
||||||
|
|
@ -91,6 +106,8 @@ struct Parser{
|
||||||
}
|
}
|
||||||
consume(Token.Type.RightBrace, "Expect '}' after class body.");
|
consume(Token.Type.RightBrace, "Expect '}' after class body.");
|
||||||
currentCompiler.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
|
if(classCompiler.hasSuperclass)
|
||||||
|
endScope();
|
||||||
|
|
||||||
}
|
}
|
||||||
void method(){
|
void method(){
|
||||||
|
|
@ -311,7 +328,7 @@ struct Parser{
|
||||||
Value nameVal = Value.from(str);
|
Value nameVal = Value.from(str);
|
||||||
return currentCompiler.makeConstant(nameVal);
|
return currentCompiler.makeConstant(nameVal);
|
||||||
}
|
}
|
||||||
void namedVariable(in ref Token name, bool canAssign){
|
void namedVariable(in Token name, bool canAssign){
|
||||||
OpCode getOp, setOp;
|
OpCode getOp, setOp;
|
||||||
int arg = resolveLocal(currentCompiler, name);
|
int arg = resolveLocal(currentCompiler, name);
|
||||||
if(arg != -1){
|
if(arg != -1){
|
||||||
|
|
@ -365,7 +382,7 @@ struct Parser{
|
||||||
compiler.upvalues ~= Upvalue(index: index, isLocal: isLocal);
|
compiler.upvalues ~= Upvalue(index: index, isLocal: isLocal);
|
||||||
return compiler.func.upvalueCount++;
|
return compiler.func.upvalueCount++;
|
||||||
}
|
}
|
||||||
void addLocal(in ref Token name){
|
void addLocal(in Token name){
|
||||||
currentCompiler.locals ~= Compiler.Local(name, -1, false);
|
currentCompiler.locals ~= Compiler.Local(name, -1, false);
|
||||||
}
|
}
|
||||||
void beginScope(){
|
void beginScope(){
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ private void strlit(Compiler* compiler, bool _){
|
||||||
Obj.String* strObj = Obj.String.copy(str);
|
Obj.String* strObj = Obj.String.copy(str);
|
||||||
compiler.emitConstant(Value.from(strObj));
|
compiler.emitConstant(Value.from(strObj));
|
||||||
}
|
}
|
||||||
private void variable(Compiler* compiler, bool canAssign){
|
void variable(Compiler* compiler, bool canAssign){
|
||||||
parser.namedVariable(parser.previous, canAssign);
|
parser.namedVariable(parser.previous, canAssign);
|
||||||
}
|
}
|
||||||
private void and(Compiler* compiler, bool _){
|
private void and(Compiler* compiler, bool _){
|
||||||
|
|
@ -113,6 +113,24 @@ private void this_(Compiler* compiler, bool _){
|
||||||
}
|
}
|
||||||
variable(compiler, false);
|
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{
|
struct ParseRule{
|
||||||
|
|
@ -172,7 +190,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
||||||
Token.Type.Or : ParseRule(null, &or, Precedence.Or),
|
Token.Type.Or : ParseRule(null, &or, Precedence.Or),
|
||||||
Token.Type.Print : ParseRule(null, null, Precedence.None),
|
Token.Type.Print : ParseRule(null, null, Precedence.None),
|
||||||
Token.Type.Return : 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.This : ParseRule(&this_, null, Precedence.None),
|
||||||
Token.Type.True : ParseRule(&literal, null, Precedence.None),
|
Token.Type.True : ParseRule(&literal, null, Precedence.None),
|
||||||
Token.Type.Var : ParseRule(null, null, Precedence.None),
|
Token.Type.Var : ParseRule(null, null, Precedence.None),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ struct Token {
|
||||||
Type type;
|
Type type;
|
||||||
string lexeme;
|
string lexeme;
|
||||||
int line;
|
int line;
|
||||||
|
static Token synth(string s) => Token(Token.Type.None, s, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Scanner scanner;
|
Scanner scanner;
|
||||||
|
|
|
||||||
|
|
@ -299,21 +299,21 @@ struct VM{
|
||||||
case OpCode.Return:
|
case OpCode.Return:
|
||||||
Value result = pop();
|
Value result = pop();
|
||||||
closeUpvalues(frame.slots);
|
closeUpvalues(frame.slots);
|
||||||
vm.frameCount--;
|
frameCount--;
|
||||||
if(vm.frameCount == 0){
|
if(frameCount == 0){
|
||||||
pop();
|
pop();
|
||||||
return InterpretResult.Ok;
|
return InterpretResult.Ok;
|
||||||
}
|
}
|
||||||
stackTop = frame.slots;
|
stackTop = frame.slots;
|
||||||
push(result);
|
push(result);
|
||||||
frame = &vm.frames[vm.frameCount - 1];
|
frame = &frames[frameCount - 1];
|
||||||
break;
|
break;
|
||||||
case OpCode.Invoke:
|
case OpCode.Invoke:
|
||||||
Obj.String* method = readString();
|
Obj.String* method = readString();
|
||||||
int argCount = readVarUint();
|
int argCount = readVarUint();
|
||||||
if(!invoke(method, argCount))
|
if(!invoke(method, argCount))
|
||||||
return InterpretResult.RuntimeError;
|
return InterpretResult.RuntimeError;
|
||||||
frame = &vm.frames[vm.frameCount - 1];
|
frame = &frames[frameCount - 1];
|
||||||
break;
|
break;
|
||||||
case OpCode.Class:
|
case OpCode.Class:
|
||||||
push(Value.from(Obj.Class.create(readString())));
|
push(Value.from(Obj.Class.create(readString())));
|
||||||
|
|
@ -321,6 +321,30 @@ struct VM{
|
||||||
case OpCode.Method:
|
case OpCode.Method:
|
||||||
defineMethod(readString());
|
defineMethod(readString());
|
||||||
break;
|
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);
|
assert(0);
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,15 @@ int main(){
|
||||||
"./test/perverseclosure.lox".match("return from outer create inner closure value ".replace(" ", "\n"));
|
"./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/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/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/invalid_syntax.lox".shouldFail(RetVal.other);
|
||||||
"./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name");
|
"./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/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable");
|
||||||
"./test/err/self_ref_vardecl.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/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_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_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass");
|
||||||
|
|
||||||
return failures;
|
return failures;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue