Methods and Initializers 28
This commit is contained in:
parent
d9dc02b92f
commit
28b0c71be1
12 changed files with 233 additions and 28 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -18,4 +18,5 @@ lox-test-*
|
||||||
.msc/
|
.msc/
|
||||||
test/test.lox
|
test/test.lox
|
||||||
.ccls-cache/
|
.ccls-cache/
|
||||||
|
.ldc2_cache/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,13 @@ 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,
|
||||||
@(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,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Chunk{
|
struct Chunk{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import clox.container.varint;
|
||||||
|
|
||||||
Parser parser;
|
Parser parser;
|
||||||
Compiler* currentCompiler;
|
Compiler* currentCompiler;
|
||||||
|
ClassCompiler* currentClass;
|
||||||
|
|
||||||
struct Upvalue{
|
struct Upvalue{
|
||||||
int index;
|
int index;
|
||||||
|
|
@ -23,7 +24,7 @@ struct Upvalue{
|
||||||
struct Compiler{
|
struct Compiler{
|
||||||
Compiler* enclosing;
|
Compiler* enclosing;
|
||||||
Obj.Function* func;
|
Obj.Function* func;
|
||||||
enum FunctionType{ None, Function, Script }
|
enum FunctionType{ None, Function, Script, Method, Initialiser }
|
||||||
FunctionType ftype = FunctionType.Script;
|
FunctionType ftype = FunctionType.Script;
|
||||||
struct Local{
|
struct Local{
|
||||||
Token name;
|
Token name;
|
||||||
|
|
@ -48,7 +49,11 @@ struct Compiler{
|
||||||
|
|
||||||
Local local = Local();
|
Local local = Local();
|
||||||
local.depth = 0;
|
local.depth = 0;
|
||||||
local.name.lexeme = "<>";
|
if(ftype != FunctionType.Function){
|
||||||
|
local.name.lexeme = "this";
|
||||||
|
} else {
|
||||||
|
local.name.lexeme = "<>";
|
||||||
|
}
|
||||||
locals ~= local;
|
locals ~= local;
|
||||||
}
|
}
|
||||||
Obj.Function* compile(){
|
Obj.Function* compile(){
|
||||||
|
|
@ -80,7 +85,11 @@ struct Compiler{
|
||||||
currentCompiler.currentChunk.write(b, parser.previous.line);
|
currentCompiler.currentChunk.write(b, parser.previous.line);
|
||||||
}
|
}
|
||||||
void emitReturn(){
|
void emitReturn(){
|
||||||
emit(OpCode.Nil, OpCode.Return);
|
if(ftype == FunctionType.Initialiser){
|
||||||
|
emit(OpCode.GetLocal, ubyte(0), OpCode.Return);
|
||||||
|
} else {
|
||||||
|
emit(OpCode.Nil, OpCode.Return);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void emitConstant(Value value){
|
void emitConstant(Value value){
|
||||||
int c = makeConstant(value);
|
int c = makeConstant(value);
|
||||||
|
|
@ -92,6 +101,10 @@ struct Compiler{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ClassCompiler{
|
||||||
|
ClassCompiler* enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
void markCompilerRoots(){
|
void markCompilerRoots(){
|
||||||
Compiler* compiler = currentCompiler;
|
Compiler* compiler = currentCompiler;
|
||||||
while(compiler !is null){
|
while(compiler !is null){
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,19 @@ private long closureInstruction(Chunk* chunk, long offset){
|
||||||
printf(" ");
|
printf(" ");
|
||||||
return offset ;
|
return offset ;
|
||||||
}
|
}
|
||||||
|
private long invokeInstruction(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);
|
||||||
|
long pl = chunk.constants[constant].print();
|
||||||
|
foreach(i; 0 .. 16-pl)
|
||||||
|
printf(" ");
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
void printHeader(Chunk* chunk, const char* name = "execution"){
|
void printHeader(Chunk* chunk, const char* name = "execution"){
|
||||||
printf(" ============== %s ==============\n", name);
|
printf(" ============== %s ==============\n", name);
|
||||||
|
|
@ -109,6 +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){
|
||||||
|
case e: return invokeInstruction(chunk, offset);
|
||||||
} else {
|
} else {
|
||||||
case e: return simpleInstruction!e(e.stringof, offset);
|
case e: return simpleInstruction!e(e.stringof, offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ void collectGarbage(){
|
||||||
vm.nextGC = conf.nextGCGrowthFunc(vm.bytesAllocated);
|
vm.nextGC = conf.nextGCGrowthFunc(vm.bytesAllocated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void mark(O)(O* o) if(is(O == Obj) || __traits(hasMember, O, "obj")){
|
void mark(O)(O* o) if(is(O == Obj) || __traits(getMember, O, "obj").offsetof == 0){
|
||||||
Obj* object = cast(Obj*)o;
|
Obj* object = cast(Obj*)o;
|
||||||
if(object is null || object.isMarked)
|
if(object is null || object.isMarked)
|
||||||
return;
|
return;
|
||||||
|
|
@ -85,6 +85,7 @@ private void markRoots(){
|
||||||
mark(upvalue);
|
mark(upvalue);
|
||||||
}
|
}
|
||||||
mark(vm.globals);
|
mark(vm.globals);
|
||||||
|
mark(vm.initString);
|
||||||
markCompilerRoots();
|
markCompilerRoots();
|
||||||
}
|
}
|
||||||
private void blacken(Obj* object){
|
private void blacken(Obj* object){
|
||||||
|
|
@ -110,6 +111,7 @@ private void blacken(Obj* object){
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Class:
|
case Obj.Type.Class:
|
||||||
Obj.Class* cls = object.asClass;
|
Obj.Class* cls = object.asClass;
|
||||||
|
mark(cls.methods);
|
||||||
mark(cls.name);
|
mark(cls.name);
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Instance:
|
case Obj.Type.Instance:
|
||||||
|
|
@ -117,6 +119,11 @@ private void blacken(Obj* object){
|
||||||
mark(ins.cls);
|
mark(ins.cls);
|
||||||
mark(ins.fields);
|
mark(ins.fields);
|
||||||
break;
|
break;
|
||||||
|
case Obj.Type.BoundMethod:
|
||||||
|
Obj.BoundMethod* bm = object.asBoundMethod;
|
||||||
|
mark(bm.receiver);
|
||||||
|
mark(bm.method);
|
||||||
|
break;
|
||||||
case Obj.Type.None: assert(0);
|
case Obj.Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ void freeObject(Obj* object){
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Class:
|
case Obj.Type.Class:
|
||||||
Obj.Class* cls = cast(Obj.Class*)object;
|
Obj.Class* cls = cast(Obj.Class*)object;
|
||||||
|
cls.methods.free();
|
||||||
FREE(cls);
|
FREE(cls);
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Instance:
|
case Obj.Type.Instance:
|
||||||
|
|
@ -108,6 +109,10 @@ void freeObject(Obj* object){
|
||||||
ins.fields.free();
|
ins.fields.free();
|
||||||
FREE(ins);
|
FREE(ins);
|
||||||
break;
|
break;
|
||||||
|
case Obj.Type.BoundMethod:
|
||||||
|
Obj.BoundMethod* bm = cast(Obj.BoundMethod*)object;
|
||||||
|
FREE(bm);
|
||||||
|
break;
|
||||||
case Obj.Type.None: assert(0);
|
case Obj.Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ module clox.obj;
|
||||||
import core.stdc.stdio : printf;
|
import core.stdc.stdio : printf;
|
||||||
import core.stdc.string : memcpy;
|
import core.stdc.string : memcpy;
|
||||||
debug import std.stdio : writeln;
|
debug import std.stdio : writeln;
|
||||||
|
import std.traits;
|
||||||
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.container.table;
|
import clox.container.table;
|
||||||
|
|
@ -13,7 +14,7 @@ import clox.gc : collectGarbage;
|
||||||
|
|
||||||
struct Obj{
|
struct Obj{
|
||||||
enum Type{
|
enum Type{
|
||||||
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance
|
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance, BoundMethod
|
||||||
}
|
}
|
||||||
Type type;
|
Type type;
|
||||||
bool isMarked;
|
bool isMarked;
|
||||||
|
|
@ -23,14 +24,9 @@ struct Obj{
|
||||||
auto as(Type type)() const pure{
|
auto as(Type type)() const pure{
|
||||||
static if(type != Type.None)
|
static if(type != Type.None)
|
||||||
assert(this.type == type);
|
assert(this.type == type);
|
||||||
static if(type == Type.String) return cast(String*)&this;
|
|
||||||
static if(type == Type.Function) return cast(Function*)&this;
|
|
||||||
static if(type == Type.NativeFunction) return cast(NativeFunction*)&this;
|
|
||||||
static if(type == Type.Closure) return cast(Closure*)&this;
|
|
||||||
static if(type == Type.Upvalue) return cast(Upvalue*)&this;
|
|
||||||
static if(type == Type.Class) return cast(Class*)&this;
|
|
||||||
static if(type == Type.Instance) return cast(Instance*)&this;
|
|
||||||
static if(type == Type.None) return &this;
|
static if(type == Type.None) return &this;
|
||||||
|
static foreach(t; EnumMembers!Type)
|
||||||
|
static if(type == t) return mixin("cast(", t.stringof, "*)&this");
|
||||||
}
|
}
|
||||||
|
|
||||||
int print(const char* strFmt = `"%s"`) const{
|
int print(const char* strFmt = `"%s"`) const{
|
||||||
|
|
@ -47,6 +43,9 @@ struct Obj{
|
||||||
return printf("<%s/%d>", closure.func.name.chars.ptr, closure.func.arity);
|
return printf("<%s/%d>", closure.func.name.chars.ptr, closure.func.arity);
|
||||||
case Type.Class: return printf("<class %s>", asClass.name.chars.ptr);
|
case Type.Class: return printf("<class %s>", asClass.name.chars.ptr);
|
||||||
case Type.Instance: return printf("<instance %s>", asInstance.cls.name.chars.ptr);
|
case Type.Instance: return printf("<instance %s>", asInstance.cls.name.chars.ptr);
|
||||||
|
case Type.BoundMethod:
|
||||||
|
Closure* closure = asBoundMethod.method;
|
||||||
|
return printf("<%s/%d>", closure.func.name.chars.ptr, closure.func.arity);
|
||||||
case Type.Upvalue:
|
case Type.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +58,7 @@ struct Obj{
|
||||||
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
|
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
|
||||||
Class* asClass() const pure => as!(Type.Class);
|
Class* asClass() const pure => as!(Type.Class);
|
||||||
Instance* asInstance() const pure => as!(Type.Instance);
|
Instance* asInstance() const pure => as!(Type.Instance);
|
||||||
|
BoundMethod* asBoundMethod() const pure => as!(Type.BoundMethod);
|
||||||
|
|
||||||
private static T* allocateObject(T)(){
|
private static T* allocateObject(T)(){
|
||||||
collectGarbage();
|
collectGarbage();
|
||||||
|
|
@ -180,8 +180,10 @@ struct Obj{
|
||||||
static enum myType = Type.Class;
|
static enum myType = Type.Class;
|
||||||
Obj obj;
|
Obj obj;
|
||||||
String* name;
|
String* name;
|
||||||
|
Table!(String*, Closure*) methods;
|
||||||
static Class* create(String* name){
|
static Class* create(String* name){
|
||||||
Class* cls = allocateObject!(typeof(this))();
|
Class* cls = allocateObject!(typeof(this))();
|
||||||
|
cls.methods = typeof(cls.methods)(4);
|
||||||
cls.name = name;
|
cls.name = name;
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +202,19 @@ struct Obj{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BoundMethod{
|
||||||
|
static enum myType = Type.BoundMethod;
|
||||||
|
Obj obj;
|
||||||
|
Value receiver;
|
||||||
|
Closure* method;
|
||||||
|
static BoundMethod* create(Value receiver, Closure* method){
|
||||||
|
BoundMethod* bm = allocateObject!(typeof(this))();
|
||||||
|
bm.receiver = receiver;
|
||||||
|
bm.method = method;
|
||||||
|
return bm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool opEquals(in ref Obj rhs) const pure{
|
bool opEquals(in ref Obj rhs) const pure{
|
||||||
if(rhs.type != type)
|
if(rhs.type != type)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -210,6 +225,7 @@ struct Obj{
|
||||||
case Type.Closure: return &this is &rhs;
|
case Type.Closure: return &this is &rhs;
|
||||||
case Type.Class: return &this is &rhs;
|
case Type.Class: return &this is &rhs;
|
||||||
case Type.Instance: return &this is &rhs;
|
case Type.Instance: return &this is &rhs;
|
||||||
|
case Type.BoundMethod: return &this is &rhs;
|
||||||
case Type.Upvalue:
|
case Type.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -222,6 +238,7 @@ struct Obj{
|
||||||
case Type.Closure: return false;
|
case Type.Closure: return false;
|
||||||
case Type.Class: return false;
|
case Type.Class: return false;
|
||||||
case Type.Instance: return false;
|
case Type.Instance: return false;
|
||||||
|
case Type.BoundMethod: return false;
|
||||||
case Type.Upvalue:
|
case Type.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +255,7 @@ struct Obj{
|
||||||
case Type.Upvalue: return "Upvalue";
|
case Type.Upvalue: return "Upvalue";
|
||||||
case Type.Class: return "Class";
|
case Type.Class: return "Class";
|
||||||
case Type.Instance: return "Instance";
|
case Type.Instance: return "Instance";
|
||||||
|
case Type.BoundMethod: return "BoundMethod";
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,19 +66,41 @@ struct Parser{
|
||||||
void funDeclaration(){
|
void funDeclaration(){
|
||||||
int global = parseVariable("Expect function name");
|
int global = parseVariable("Expect function name");
|
||||||
markInitialised();
|
markInitialised();
|
||||||
func();
|
func(Compiler.FunctionType.Function);
|
||||||
defineVariable(global);
|
defineVariable(global);
|
||||||
}
|
}
|
||||||
void classDeclaration(){
|
void classDeclaration(){
|
||||||
consume(Token.Type.Identifier, "Expect class name.");
|
consume(Token.Type.Identifier, "Expect class name.");
|
||||||
int nameConstant = identifierConstant(parser.previous);
|
Token className = previous;
|
||||||
|
int nameConstant = identifierConstant(previous);
|
||||||
declareVariable();
|
declareVariable();
|
||||||
|
|
||||||
currentCompiler.emit(OpCode.Class, VarUint(nameConstant).bytes);
|
currentCompiler.emit(OpCode.Class, VarUint(nameConstant).bytes);
|
||||||
defineVariable(nameConstant);
|
defineVariable(nameConstant);
|
||||||
|
|
||||||
|
ClassCompiler classCompiler;
|
||||||
|
classCompiler.enclosing = currentClass;
|
||||||
|
currentClass = &classCompiler;
|
||||||
|
scope(exit)
|
||||||
|
currentClass = currentClass.enclosing;
|
||||||
|
|
||||||
|
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)){
|
||||||
|
method();
|
||||||
|
}
|
||||||
consume(Token.Type.RightBrace, "Expect '}' after class body.");
|
consume(Token.Type.RightBrace, "Expect '}' after class body.");
|
||||||
|
currentCompiler.emit(OpCode.Pop);
|
||||||
|
|
||||||
|
}
|
||||||
|
void method(){
|
||||||
|
consume(Token.Type.Identifier, "Expect method name.");
|
||||||
|
int constant = identifierConstant(previous);
|
||||||
|
Compiler.FunctionType type = Compiler.FunctionType.Method;
|
||||||
|
if(previous.lexeme == "init")
|
||||||
|
type = Compiler.FunctionType.Initialiser;
|
||||||
|
func(type);
|
||||||
|
currentCompiler.emit(OpCode.Method, VarUint(constant).bytes);
|
||||||
}
|
}
|
||||||
void declaration(){
|
void declaration(){
|
||||||
if(match(Token.Type.Var))
|
if(match(Token.Type.Var))
|
||||||
|
|
@ -89,7 +111,7 @@ struct Parser{
|
||||||
classDeclaration();
|
classDeclaration();
|
||||||
else
|
else
|
||||||
statement();
|
statement();
|
||||||
if(parser.panicMode)
|
if(panicMode)
|
||||||
synchronise();
|
synchronise();
|
||||||
}
|
}
|
||||||
void statement(){
|
void statement(){
|
||||||
|
|
@ -122,6 +144,8 @@ struct Parser{
|
||||||
if(match(Token.Type.Semicolon)){
|
if(match(Token.Type.Semicolon)){
|
||||||
currentCompiler.emitReturn();
|
currentCompiler.emitReturn();
|
||||||
} else {
|
} else {
|
||||||
|
if(currentCompiler.ftype == Compiler.FunctionType.Initialiser)
|
||||||
|
error("Can't return a value from an initializer.");
|
||||||
expression();
|
expression();
|
||||||
consume(Token.Type.Semicolon, "Expect ';' after return value.");
|
consume(Token.Type.Semicolon, "Expect ';' after return value.");
|
||||||
currentCompiler.emit(OpCode.Return);
|
currentCompiler.emit(OpCode.Return);
|
||||||
|
|
@ -206,9 +230,9 @@ struct Parser{
|
||||||
declaration();
|
declaration();
|
||||||
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
||||||
}
|
}
|
||||||
void func(){
|
void func(Compiler.FunctionType type){
|
||||||
Compiler compiler;
|
Compiler compiler;
|
||||||
compiler.initialise(Compiler.FunctionType.Function);
|
compiler.initialise(type);
|
||||||
scope(exit)
|
scope(exit)
|
||||||
compiler.free();
|
compiler.free();
|
||||||
beginScope();
|
beginScope();
|
||||||
|
|
@ -266,7 +290,7 @@ struct Parser{
|
||||||
void declareVariable(){
|
void declareVariable(){
|
||||||
if(currentCompiler.scopeDepth == 0)
|
if(currentCompiler.scopeDepth == 0)
|
||||||
return;
|
return;
|
||||||
const Token* name = &parser.previous;
|
const Token* name = &previous;
|
||||||
foreach_reverse(const ref local; currentCompiler.locals){
|
foreach_reverse(const ref local; currentCompiler.locals){
|
||||||
if(local.depth != -1 && local.depth < currentCompiler.scopeDepth)
|
if(local.depth != -1 && local.depth < currentCompiler.scopeDepth)
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,20 @@ private void dot(Compiler* compiler, bool canAssign){
|
||||||
if(canAssign && parser.match(Token.Type.Equal)){
|
if(canAssign && parser.match(Token.Type.Equal)){
|
||||||
parser.expression();
|
parser.expression();
|
||||||
compiler.emit(OpCode.SetProp, name.bytes);
|
compiler.emit(OpCode.SetProp, name.bytes);
|
||||||
|
} else if(parser.match(Token.Type.LeftParen)){
|
||||||
|
auto argCount = VarUint(parser.argumentList());
|
||||||
|
compiler.emit(OpCode.Invoke, name.bytes, argCount.bytes);
|
||||||
} else {
|
} else {
|
||||||
compiler.emit(OpCode.GetProp, name.bytes);
|
compiler.emit(OpCode.GetProp, name.bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void this_(Compiler* compiler, bool _){
|
||||||
|
if(currentClass == null){
|
||||||
|
parser.error("Can't use 'this' outside of a class.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
variable(compiler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ParseRule{
|
struct ParseRule{
|
||||||
|
|
@ -163,7 +173,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
||||||
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(null, null, Precedence.None),
|
||||||
Token.Type.This : ParseRule(null, 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),
|
||||||
Token.Type.While : ParseRule(null, null, Precedence.None),
|
Token.Type.While : ParseRule(null, null, Precedence.None),
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ struct VM{
|
||||||
debug(printCode) bool printCode;
|
debug(printCode) bool printCode;
|
||||||
debug(traceExec) bool traceExec;
|
debug(traceExec) bool traceExec;
|
||||||
bool isREPL, dontRun;
|
bool isREPL, dontRun;
|
||||||
|
Obj.String* initString;
|
||||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||||
struct CallFrame{
|
struct CallFrame{
|
||||||
Obj.Closure* closure;
|
Obj.Closure* closure;
|
||||||
|
|
@ -43,6 +44,7 @@ struct VM{
|
||||||
globals = typeof(globals)(8);
|
globals = typeof(globals)(8);
|
||||||
greyStack.initialise();
|
greyStack.initialise();
|
||||||
stackTop = &stack[0];
|
stackTop = &stack[0];
|
||||||
|
initString = Obj.String.copy("init");
|
||||||
|
|
||||||
defineNative("clock", (int argCount, Value* args){
|
defineNative("clock", (int argCount, Value* args){
|
||||||
import core.stdc.time;
|
import core.stdc.time;
|
||||||
|
|
@ -84,6 +86,7 @@ struct VM{
|
||||||
fputs("\n", stderr);
|
fputs("\n", stderr);
|
||||||
printStackTrace();
|
printStackTrace();
|
||||||
}
|
}
|
||||||
|
private Value peek(int distance = 0) => stackTop[-1 - distance];
|
||||||
InterpretResult run(){
|
InterpretResult run(){
|
||||||
CallFrame* frame = &frames[frameCount - 1];
|
CallFrame* frame = &frames[frameCount - 1];
|
||||||
ref ip() => frame.ip;
|
ref ip() => frame.ip;
|
||||||
|
|
@ -104,7 +107,6 @@ struct VM{
|
||||||
}
|
}
|
||||||
Value readConstant() => chunk.constants[readVarUint()];
|
Value readConstant() => chunk.constants[readVarUint()];
|
||||||
Obj.String* readString() => readConstant().asObj.asString;
|
Obj.String* readString() => readConstant().asObj.asString;
|
||||||
Value peek(int distance = 0) => stackTop[-1 - distance];
|
|
||||||
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
||||||
bool checkSameType()() => peek(0).type == peek(1).type;
|
bool checkSameType()() => peek(0).type == peek(1).type;
|
||||||
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||||
|
|
@ -209,8 +211,10 @@ struct VM{
|
||||||
push(*value);
|
push(*value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
runtimeError("Undefined property '%s'.", name.chars.ptr);
|
if(!bindMethod(ins.cls, name)){
|
||||||
return InterpretResult.RuntimeError;
|
return InterpretResult.RuntimeError;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OpCode.SetProp:
|
case OpCode.SetProp:
|
||||||
if(!peek(1).isType(Obj.Type.Instance)){
|
if(!peek(1).isType(Obj.Type.Instance)){
|
||||||
runtimeError("Only instances have fields.");
|
runtimeError("Only instances have fields.");
|
||||||
|
|
@ -304,9 +308,19 @@ struct VM{
|
||||||
push(result);
|
push(result);
|
||||||
frame = &vm.frames[vm.frameCount - 1];
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
break;
|
break;
|
||||||
|
case OpCode.Invoke:
|
||||||
|
Obj.String* method = readString();
|
||||||
|
int argCount = readVarUint();
|
||||||
|
if(!invoke(method, argCount))
|
||||||
|
return InterpretResult.RuntimeError;
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
case OpCode.Class:
|
case OpCode.Class:
|
||||||
push(Value.from(Obj.Class.create(readString())));
|
push(Value.from(Obj.Class.create(readString())));
|
||||||
break;
|
break;
|
||||||
|
case OpCode.Method:
|
||||||
|
defineMethod(readString());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(0);
|
assert(0);
|
||||||
|
|
@ -366,9 +380,20 @@ struct VM{
|
||||||
vm.stackTop -= argCount + 1;
|
vm.stackTop -= argCount + 1;
|
||||||
push(result);
|
push(result);
|
||||||
return true;
|
return true;
|
||||||
|
case Obj.Type.BoundMethod:
|
||||||
|
Obj.BoundMethod* bound = obj.asBoundMethod;
|
||||||
|
stackTop[-argCount - 1] = bound.receiver;
|
||||||
|
return call(bound.method, argCount);
|
||||||
|
return true;
|
||||||
case Obj.Type.Class:
|
case Obj.Type.Class:
|
||||||
Obj.Class* cls = obj.asClass;
|
Obj.Class* cls = obj.asClass;
|
||||||
vm.stackTop[-argCount - 1] = Value.from(Obj.Instance.create(cls));
|
vm.stackTop[-argCount - 1] = Value.from(Obj.Instance.create(cls));
|
||||||
|
if(Obj.Closure** initialiser = cls.methods[vm.initString]){
|
||||||
|
return call(*initialiser, argCount);
|
||||||
|
} else if(argCount != 0){
|
||||||
|
runtimeError("Expected 0 arguments but got %d.", argCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
case Obj.Type.Instance: break;
|
case Obj.Type.Instance: break;
|
||||||
case Obj.Type.String: break;
|
case Obj.Type.String: break;
|
||||||
|
|
@ -403,5 +428,40 @@ struct VM{
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void defineMethod(Obj.String* name){
|
||||||
|
Value method = peek(0);
|
||||||
|
Obj.Class* cls = peek(1).asObj.asClass;
|
||||||
|
cls.methods[name] = method.asObj.asClosure;
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
bool bindMethod(Obj.Class* cls, Obj.String* name){
|
||||||
|
if(Obj.Closure** method = cls.methods[name]){
|
||||||
|
Obj.BoundMethod* bound = Obj.BoundMethod.create(peek(0), *method);
|
||||||
|
pop();
|
||||||
|
push(Value.from(bound));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
runtimeError("Undefined property '%s'.", name.chars.ptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool invoke(Obj.String* name, int argCount){
|
||||||
|
Value receiver = peek(argCount);
|
||||||
|
if(!receiver.isType(Obj.Type.Instance)){
|
||||||
|
runtimeError("Only instances have methods.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Obj.Instance* instance = receiver.asObj.asInstance;
|
||||||
|
if(Value* value = instance.fields[name]){
|
||||||
|
vm.stackTop[-argCount - 1] = *value;
|
||||||
|
return callValue(*value, argCount);
|
||||||
|
}
|
||||||
|
return invokeFromClass(instance.cls, name, argCount);
|
||||||
|
}
|
||||||
|
bool invokeFromClass(Obj.Class* cls, Obj.String* name, int argCount){
|
||||||
|
if(Obj.Closure** method = cls.methods[name])
|
||||||
|
return call(*method, argCount);
|
||||||
|
runtimeError("Undefined property '%s'.", name.chars.ptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
test/all.d
32
test/all.d
|
|
@ -7,7 +7,9 @@ import std.conv;
|
||||||
import std.string, std.format;
|
import std.string, std.format;
|
||||||
import std.algorithm, std.range;
|
import std.algorithm, std.range;
|
||||||
|
|
||||||
void main(){
|
int failures;
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
|
||||||
"./test/func.lox".match("-1\n6\n1\n");
|
"./test/func.lox".match("-1\n6\n1\n");
|
||||||
"./test/bigsum.lox".match("125250\n");
|
"./test/bigsum.lox".match("125250\n");
|
||||||
|
|
@ -26,7 +28,8 @@ void main(){
|
||||||
"./test/fib_recursive.lox".match(fib(34));
|
"./test/fib_recursive.lox".match(fib(34));
|
||||||
"./test/fib_closure.lox".match(fib(34));
|
"./test/fib_closure.lox".match(fib(34));
|
||||||
"./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/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);
|
||||||
|
|
@ -36,6 +39,8 @@ void main(){
|
||||||
"./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;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RetVal{
|
enum RetVal{
|
||||||
|
|
@ -56,12 +61,27 @@ string fib(uint n){
|
||||||
auto run(string file, Config.Flags f = Config.Flags.stderrPassThrough) => [ "./lox", file ].execute(null, Config(f));
|
auto run(string file, Config.Flags f = Config.Flags.stderrPassThrough) => [ "./lox", file ].execute(null, Config(f));
|
||||||
void match(string file, string correct){
|
void match(string file, string correct){
|
||||||
auto res = file.run.output;
|
auto res = file.run.output;
|
||||||
assert(res == correct, "Match %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, res, correct));
|
if(res != correct){
|
||||||
|
stderr.writeln("Match %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, res, correct));
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void shouldFail(string file, int code = 1, string msg = null){
|
void shouldFail(string file, int code = 1, string msg = null){
|
||||||
auto c = file.run(Config.Flags.none);
|
auto c = file.run(Config.Flags.none);
|
||||||
assert(c.status == code, "Expected %s to fail with code %d but got %d".format(file, code, c.status));
|
if(!(c.status == code)){
|
||||||
assert(!msg || c.output.toLower.indexOf(msg.toLower) >= 0, "ShouldFail %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, c.output, msg));
|
stderr.writeln("Expected %s to fail with code %d but got %d".format(file, code, c.status));
|
||||||
assert(c.output.indexOf("_Dmain") == -1, "ShouldFail %s got D exception\n%s".format(file, c.output));
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!(!msg || c.output.toLower.indexOf(msg.toLower) >= 0)){
|
||||||
|
stderr.writeln("ShouldFail %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, c.output, msg));
|
||||||
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!(c.output.indexOf("_Dmain") == -1)){
|
||||||
|
stderr.writeln("ShouldFail %s got D exception\n%s".format(file, c.output));
|
||||||
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
test/methods.lox
Normal file
29
test/methods.lox
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
class CoffeeMaker{
|
||||||
|
init(coffee){
|
||||||
|
this.coffee = coffee;
|
||||||
|
}
|
||||||
|
|
||||||
|
brew(){
|
||||||
|
print "Enjoy your cup of " + this.coffee;
|
||||||
|
|
||||||
|
// No reusing the grounds!
|
||||||
|
this.coffee = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maker = CoffeeMaker("coffee and chicory");
|
||||||
|
maker.brew();
|
||||||
|
|
||||||
|
class Oops{
|
||||||
|
init(){
|
||||||
|
fun f(){
|
||||||
|
print "not a method";
|
||||||
|
}
|
||||||
|
this.field = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oops = Oops();
|
||||||
|
oops.field();
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue