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/
|
||||
test/test.lox
|
||||
.ccls-cache/
|
||||
.ldc2_cache/
|
||||
|
||||
|
|
|
|||
|
|
@ -56,10 +56,13 @@ enum OpCode : ubyte{
|
|||
@(OpColour("000", "200", "100")) Print,
|
||||
|
||||
@OpCall @(OpColour("250", "200", "250")) Call,
|
||||
@(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,
|
||||
}
|
||||
|
||||
struct Chunk{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import clox.container.varint;
|
|||
|
||||
Parser parser;
|
||||
Compiler* currentCompiler;
|
||||
ClassCompiler* currentClass;
|
||||
|
||||
struct Upvalue{
|
||||
int index;
|
||||
|
|
@ -23,7 +24,7 @@ struct Upvalue{
|
|||
struct Compiler{
|
||||
Compiler* enclosing;
|
||||
Obj.Function* func;
|
||||
enum FunctionType{ None, Function, Script }
|
||||
enum FunctionType{ None, Function, Script, Method, Initialiser }
|
||||
FunctionType ftype = FunctionType.Script;
|
||||
struct Local{
|
||||
Token name;
|
||||
|
|
@ -48,7 +49,11 @@ struct Compiler{
|
|||
|
||||
Local local = Local();
|
||||
local.depth = 0;
|
||||
if(ftype != FunctionType.Function){
|
||||
local.name.lexeme = "this";
|
||||
} else {
|
||||
local.name.lexeme = "<>";
|
||||
}
|
||||
locals ~= local;
|
||||
}
|
||||
Obj.Function* compile(){
|
||||
|
|
@ -80,8 +85,12 @@ struct Compiler{
|
|||
currentCompiler.currentChunk.write(b, parser.previous.line);
|
||||
}
|
||||
void emitReturn(){
|
||||
if(ftype == FunctionType.Initialiser){
|
||||
emit(OpCode.GetLocal, ubyte(0), OpCode.Return);
|
||||
} else {
|
||||
emit(OpCode.Nil, OpCode.Return);
|
||||
}
|
||||
}
|
||||
void emitConstant(Value value){
|
||||
int c = makeConstant(value);
|
||||
emit(OpCode.Constant, VarUint(c).bytes);
|
||||
|
|
@ -92,6 +101,10 @@ struct Compiler{
|
|||
}
|
||||
}
|
||||
|
||||
struct ClassCompiler{
|
||||
ClassCompiler* enclosing;
|
||||
}
|
||||
|
||||
void markCompilerRoots(){
|
||||
Compiler* compiler = currentCompiler;
|
||||
while(compiler !is null){
|
||||
|
|
|
|||
|
|
@ -70,6 +70,19 @@ private long closureInstruction(Chunk* chunk, long offset){
|
|||
printf(" ");
|
||||
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"){
|
||||
printf(" ============== %s ==============\n", name);
|
||||
|
|
@ -109,6 +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 {
|
||||
case e: return simpleInstruction!e(e.stringof, offset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ void collectGarbage(){
|
|||
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;
|
||||
if(object is null || object.isMarked)
|
||||
return;
|
||||
|
|
@ -85,6 +85,7 @@ private void markRoots(){
|
|||
mark(upvalue);
|
||||
}
|
||||
mark(vm.globals);
|
||||
mark(vm.initString);
|
||||
markCompilerRoots();
|
||||
}
|
||||
private void blacken(Obj* object){
|
||||
|
|
@ -110,6 +111,7 @@ private void blacken(Obj* object){
|
|||
break;
|
||||
case Obj.Type.Class:
|
||||
Obj.Class* cls = object.asClass;
|
||||
mark(cls.methods);
|
||||
mark(cls.name);
|
||||
break;
|
||||
case Obj.Type.Instance:
|
||||
|
|
@ -117,6 +119,11 @@ private void blacken(Obj* object){
|
|||
mark(ins.cls);
|
||||
mark(ins.fields);
|
||||
break;
|
||||
case Obj.Type.BoundMethod:
|
||||
Obj.BoundMethod* bm = object.asBoundMethod;
|
||||
mark(bm.receiver);
|
||||
mark(bm.method);
|
||||
break;
|
||||
case Obj.Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ void freeObject(Obj* object){
|
|||
break;
|
||||
case Obj.Type.Class:
|
||||
Obj.Class* cls = cast(Obj.Class*)object;
|
||||
cls.methods.free();
|
||||
FREE(cls);
|
||||
break;
|
||||
case Obj.Type.Instance:
|
||||
|
|
@ -108,6 +109,10 @@ void freeObject(Obj* object){
|
|||
ins.fields.free();
|
||||
FREE(ins);
|
||||
break;
|
||||
case Obj.Type.BoundMethod:
|
||||
Obj.BoundMethod* bm = cast(Obj.BoundMethod*)object;
|
||||
FREE(bm);
|
||||
break;
|
||||
case Obj.Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module clox.obj;
|
|||
import core.stdc.stdio : printf;
|
||||
import core.stdc.string : memcpy;
|
||||
debug import std.stdio : writeln;
|
||||
import std.traits;
|
||||
|
||||
import clox.chunk;
|
||||
import clox.container.table;
|
||||
|
|
@ -13,7 +14,7 @@ import clox.gc : collectGarbage;
|
|||
|
||||
struct Obj{
|
||||
enum Type{
|
||||
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance
|
||||
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance, BoundMethod
|
||||
}
|
||||
Type type;
|
||||
bool isMarked;
|
||||
|
|
@ -23,14 +24,9 @@ struct Obj{
|
|||
auto as(Type type)() const pure{
|
||||
static if(type != Type.None)
|
||||
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 foreach(t; EnumMembers!Type)
|
||||
static if(type == t) return mixin("cast(", t.stringof, "*)&this");
|
||||
}
|
||||
|
||||
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);
|
||||
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.BoundMethod:
|
||||
Closure* closure = asBoundMethod.method;
|
||||
return printf("<%s/%d>", closure.func.name.chars.ptr, closure.func.arity);
|
||||
case Type.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -59,6 +58,7 @@ struct Obj{
|
|||
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
|
||||
Class* asClass() const pure => as!(Type.Class);
|
||||
Instance* asInstance() const pure => as!(Type.Instance);
|
||||
BoundMethod* asBoundMethod() const pure => as!(Type.BoundMethod);
|
||||
|
||||
private static T* allocateObject(T)(){
|
||||
collectGarbage();
|
||||
|
|
@ -180,8 +180,10 @@ struct Obj{
|
|||
static enum myType = Type.Class;
|
||||
Obj obj;
|
||||
String* name;
|
||||
Table!(String*, Closure*) methods;
|
||||
static Class* create(String* name){
|
||||
Class* cls = allocateObject!(typeof(this))();
|
||||
cls.methods = typeof(cls.methods)(4);
|
||||
cls.name = name;
|
||||
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{
|
||||
if(rhs.type != type)
|
||||
return false;
|
||||
|
|
@ -210,6 +225,7 @@ struct Obj{
|
|||
case Type.Closure: return &this is &rhs;
|
||||
case Type.Class: return &this is &rhs;
|
||||
case Type.Instance: return &this is &rhs;
|
||||
case Type.BoundMethod: return &this is &rhs;
|
||||
case Type.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -222,6 +238,7 @@ struct Obj{
|
|||
case Type.Closure: return false;
|
||||
case Type.Class: return false;
|
||||
case Type.Instance: return false;
|
||||
case Type.BoundMethod: return false;
|
||||
case Type.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -238,6 +255,7 @@ struct Obj{
|
|||
case Type.Upvalue: return "Upvalue";
|
||||
case Type.Class: return "Class";
|
||||
case Type.Instance: return "Instance";
|
||||
case Type.BoundMethod: return "BoundMethod";
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,19 +66,41 @@ struct Parser{
|
|||
void funDeclaration(){
|
||||
int global = parseVariable("Expect function name");
|
||||
markInitialised();
|
||||
func();
|
||||
func(Compiler.FunctionType.Function);
|
||||
defineVariable(global);
|
||||
}
|
||||
void classDeclaration(){
|
||||
consume(Token.Type.Identifier, "Expect class name.");
|
||||
int nameConstant = identifierConstant(parser.previous);
|
||||
Token className = previous;
|
||||
int nameConstant = identifierConstant(previous);
|
||||
declareVariable();
|
||||
|
||||
currentCompiler.emit(OpCode.Class, VarUint(nameConstant).bytes);
|
||||
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.");
|
||||
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF)){
|
||||
method();
|
||||
}
|
||||
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(){
|
||||
if(match(Token.Type.Var))
|
||||
|
|
@ -89,7 +111,7 @@ struct Parser{
|
|||
classDeclaration();
|
||||
else
|
||||
statement();
|
||||
if(parser.panicMode)
|
||||
if(panicMode)
|
||||
synchronise();
|
||||
}
|
||||
void statement(){
|
||||
|
|
@ -122,6 +144,8 @@ struct Parser{
|
|||
if(match(Token.Type.Semicolon)){
|
||||
currentCompiler.emitReturn();
|
||||
} else {
|
||||
if(currentCompiler.ftype == Compiler.FunctionType.Initialiser)
|
||||
error("Can't return a value from an initializer.");
|
||||
expression();
|
||||
consume(Token.Type.Semicolon, "Expect ';' after return value.");
|
||||
currentCompiler.emit(OpCode.Return);
|
||||
|
|
@ -206,9 +230,9 @@ struct Parser{
|
|||
declaration();
|
||||
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
||||
}
|
||||
void func(){
|
||||
void func(Compiler.FunctionType type){
|
||||
Compiler compiler;
|
||||
compiler.initialise(Compiler.FunctionType.Function);
|
||||
compiler.initialise(type);
|
||||
scope(exit)
|
||||
compiler.free();
|
||||
beginScope();
|
||||
|
|
@ -266,7 +290,7 @@ struct Parser{
|
|||
void declareVariable(){
|
||||
if(currentCompiler.scopeDepth == 0)
|
||||
return;
|
||||
const Token* name = &parser.previous;
|
||||
const Token* name = &previous;
|
||||
foreach_reverse(const ref local; currentCompiler.locals){
|
||||
if(local.depth != -1 && local.depth < currentCompiler.scopeDepth)
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -99,10 +99,20 @@ private void dot(Compiler* compiler, bool canAssign){
|
|||
if(canAssign && parser.match(Token.Type.Equal)){
|
||||
parser.expression();
|
||||
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 {
|
||||
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{
|
||||
|
|
@ -163,7 +173,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
|||
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.This : ParseRule(null, 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),
|
||||
Token.Type.While : ParseRule(null, null, Precedence.None),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ struct VM{
|
|||
debug(printCode) bool printCode;
|
||||
debug(traceExec) bool traceExec;
|
||||
bool isREPL, dontRun;
|
||||
Obj.String* initString;
|
||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||
struct CallFrame{
|
||||
Obj.Closure* closure;
|
||||
|
|
@ -43,6 +44,7 @@ struct VM{
|
|||
globals = typeof(globals)(8);
|
||||
greyStack.initialise();
|
||||
stackTop = &stack[0];
|
||||
initString = Obj.String.copy("init");
|
||||
|
||||
defineNative("clock", (int argCount, Value* args){
|
||||
import core.stdc.time;
|
||||
|
|
@ -84,6 +86,7 @@ struct VM{
|
|||
fputs("\n", stderr);
|
||||
printStackTrace();
|
||||
}
|
||||
private Value peek(int distance = 0) => stackTop[-1 - distance];
|
||||
InterpretResult run(){
|
||||
CallFrame* frame = &frames[frameCount - 1];
|
||||
ref ip() => frame.ip;
|
||||
|
|
@ -104,7 +107,6 @@ struct VM{
|
|||
}
|
||||
Value readConstant() => chunk.constants[readVarUint()];
|
||||
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 checkSameType()() => peek(0).type == peek(1).type;
|
||||
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||
|
|
@ -209,8 +211,10 @@ struct VM{
|
|||
push(*value);
|
||||
break;
|
||||
}
|
||||
runtimeError("Undefined property '%s'.", name.chars.ptr);
|
||||
if(!bindMethod(ins.cls, name)){
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
break;
|
||||
case OpCode.SetProp:
|
||||
if(!peek(1).isType(Obj.Type.Instance)){
|
||||
runtimeError("Only instances have fields.");
|
||||
|
|
@ -304,9 +308,19 @@ struct VM{
|
|||
push(result);
|
||||
frame = &vm.frames[vm.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];
|
||||
break;
|
||||
case OpCode.Class:
|
||||
push(Value.from(Obj.Class.create(readString())));
|
||||
break;
|
||||
case OpCode.Method:
|
||||
defineMethod(readString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(0);
|
||||
|
|
@ -366,9 +380,20 @@ struct VM{
|
|||
vm.stackTop -= argCount + 1;
|
||||
push(result);
|
||||
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:
|
||||
Obj.Class* cls = obj.asClass;
|
||||
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;
|
||||
case Obj.Type.Instance: break;
|
||||
case Obj.Type.String: break;
|
||||
|
|
@ -403,5 +428,40 @@ struct VM{
|
|||
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.algorithm, std.range;
|
||||
|
||||
void main(){
|
||||
int failures;
|
||||
|
||||
int main(){
|
||||
|
||||
"./test/func.lox".match("-1\n6\n1\n");
|
||||
"./test/bigsum.lox".match("125250\n");
|
||||
|
|
@ -26,7 +28,8 @@ void main(){
|
|||
"./test/fib_recursive.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/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/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/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;
|
||||
}
|
||||
|
||||
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));
|
||||
void match(string file, string correct){
|
||||
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){
|
||||
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));
|
||||
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));
|
||||
assert(c.output.indexOf("_Dmain") == -1, "ShouldFail %s got D exception\n%s".format(file, c.output));
|
||||
if(!(c.status == code)){
|
||||
stderr.writeln("Expected %s to fail with code %d but got %d".format(file, code, c.status));
|
||||
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