Superclasses 29

This commit is contained in:
nazrin 2025-06-15 11:15:24 +00:00
parent 28b0c71be1
commit 51bc1395f8
10 changed files with 88 additions and 18 deletions

View file

@ -18,8 +18,7 @@ configuration "clox-dbg" {
sourcePaths "src/clox"
buildRequirements "requireBoundsCheck" "requireContracts"
debugVersions "printCode"
debugVersions "traceExec"
debugVersions "printCode" "traceExec"
/* debugVersions "stressGC" */
/* debugVersions "disableGC" */

View file

@ -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{

View file

@ -103,6 +103,7 @@ struct Compiler{
struct ClassCompiler{
ClassCompiler* enclosing;
bool hasSuperclass;
}
void markCompilerRoots(){

View file

@ -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){

View file

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

View file

@ -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(){

View file

@ -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),

View file

@ -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;

View file

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

View file

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