Classes and Instances 27
This commit is contained in:
parent
6d5dff6e3d
commit
d9dc02b92f
13 changed files with 271 additions and 63 deletions
|
|
@ -36,6 +36,9 @@ enum OpCode : ubyte{
|
|||
@OpJump @(OpColour("000", "200", "000")) JumpIfFalse,
|
||||
@OpJump @(OpColour("010", "255", "000")) Loop,
|
||||
|
||||
@OpConst @(OpColour("060", "010", "150")) GetProp,
|
||||
@OpConst @(OpColour("060", "000", "150")) SetProp,
|
||||
|
||||
@(OpColour("255", "100", "100")) Equal,
|
||||
@(OpColour("255", "100", "100")) Greater,
|
||||
@(OpColour("255", "100", "100")) Less,
|
||||
|
|
@ -56,6 +59,7 @@ enum OpCode : ubyte{
|
|||
@(OpColour("250", "200", "250")) Closure,
|
||||
@(OpColour("250", "200", "050")) CloseUpvalue,
|
||||
@(OpColour("250", "190", "200")) Return,
|
||||
@OpConst @(OpColour("050", "190", "200")) Class,
|
||||
}
|
||||
|
||||
struct Chunk{
|
||||
|
|
|
|||
11
src/clox/conf.d
Normal file
11
src/clox/conf.d
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
module clox.conf;
|
||||
|
||||
size_t tablecheckSizeFunc(size_t alive, size_t pl) => alive+1 < pl/2;
|
||||
size_t tableGrowthFunc(size_t size) => size < 8 ? 8 : (cast(size_t)(cast(double)size * 1.5));
|
||||
size_t nextGCGrowthFunc(size_t alloced) => alloced * 2;
|
||||
|
||||
enum vmDefaultNextGC = 1024 * 4;
|
||||
enum vmStackSize = 1024;
|
||||
enum vmNCallFrames = 256;
|
||||
|
||||
|
|
@ -46,11 +46,6 @@ struct DynArray(T){
|
|||
assert(count > 0);
|
||||
return ptr[--_count];
|
||||
}
|
||||
void reset(){
|
||||
_count = 0;
|
||||
debug foreach(ref item; this[])
|
||||
item = T.init;
|
||||
}
|
||||
void free(){
|
||||
if(ptr)
|
||||
FREE_ARRAY!T(ptr, capacity);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
module clox.container.table;
|
||||
|
||||
import core.stdc.stdlib : calloc, cfree = free;
|
||||
import core.stdc.string;
|
||||
import std.algorithm : min, max;
|
||||
import std.traits : isArray;
|
||||
debug import std.stdio : writeln;
|
||||
|
||||
import clox.memory;
|
||||
import clox.memorydbg;
|
||||
import clox.obj;
|
||||
import clox.value;
|
||||
import clox.container.dynarray;
|
||||
import conf = clox.conf;
|
||||
|
||||
struct Table(K, V){
|
||||
alias Hash = uint;
|
||||
|
|
@ -21,6 +22,7 @@ struct Table(K, V){
|
|||
bool isAlive() const => state == State.Alive;
|
||||
bool isFree() const => state != State.Alive;
|
||||
void remove(){
|
||||
this = Entry.init;
|
||||
state = State.Dead;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,9 +46,11 @@ struct Table(K, V){
|
|||
cfree(cast(void*)p.ptr);
|
||||
}
|
||||
private void checkResize(){
|
||||
if(alive+1 < pool.length/2)
|
||||
if(conf.tablecheckSizeFunc(alive, pool.length))
|
||||
return;
|
||||
size_t newSize = pool.length * 2;
|
||||
rehash(conf.tableGrowthFunc(pool.length));
|
||||
}
|
||||
private void rehash(in size_t newSize){
|
||||
auto newTable = Table!(K, V)(newSize);
|
||||
foreach(Entry entry; pool){
|
||||
if(entry.isAlive)
|
||||
|
|
@ -81,14 +85,15 @@ struct Table(K, V){
|
|||
Hash hash = getHash(key);
|
||||
size_t index = hash % pool.length;
|
||||
while(true){
|
||||
const entry = pool[index];
|
||||
const Entry* entry = &pool[index];
|
||||
if(entry.isAlive && entry.key == key)
|
||||
break;
|
||||
if(entry.isFree)
|
||||
if(entry.isFree){
|
||||
alive++;
|
||||
break;
|
||||
}
|
||||
index = (index+1) % pool.length;
|
||||
}
|
||||
alive++;
|
||||
pool[index] = Entry(cast(K)key, val);
|
||||
}
|
||||
V* opIndex(in K key){
|
||||
|
|
@ -97,21 +102,26 @@ struct Table(K, V){
|
|||
return &entry.val;
|
||||
return null;
|
||||
}
|
||||
void remove(in K key){
|
||||
bool remove(in K key){
|
||||
Entry* entry = find(key);
|
||||
if(entry){
|
||||
entry.remove();
|
||||
alive--;
|
||||
remove(entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void remove(Entry* entry){
|
||||
assert(entry);
|
||||
entry.remove();
|
||||
alive--;
|
||||
}
|
||||
}
|
||||
|
||||
void removeWhite(Table!(char[], Obj.String*) table){
|
||||
void removeWhite(ref Table!(char[], Obj.String*) table){
|
||||
import std.stdio, std.algorithm;
|
||||
foreach(ref entry; table.pool){
|
||||
if(entry.state == table.State.Alive && !entry.val.obj.isMarked){
|
||||
entry.remove();
|
||||
table.alive--;
|
||||
}
|
||||
if(entry.state == table.State.Alive && !entry.val.obj.isMarked)
|
||||
table.remove(&entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,20 +157,24 @@ unittest{
|
|||
assert(tbl["hello"] == null);
|
||||
}
|
||||
|
||||
tbl.remove("l");
|
||||
tbl["l"] = 3;
|
||||
tbl["m"] = 2;
|
||||
tbl["s"] = 1;
|
||||
assert(*tbl["l"] == 3);
|
||||
assert(*tbl["m"] == 2);
|
||||
assert(*tbl["s"] == 1);
|
||||
foreach(i; 0 .. 3){
|
||||
tbl.remove("l");
|
||||
tbl["l"] = 3;
|
||||
tbl["m"] = 2;
|
||||
tbl["s"] = 1;
|
||||
assert(*tbl["l"] == 3);
|
||||
assert(*tbl["m"] == 2);
|
||||
assert(*tbl["s"] == 1);
|
||||
|
||||
tbl["m"] = 20;
|
||||
tbl["l"] = 30;
|
||||
tbl["s"] = 10;
|
||||
assert(*tbl["m"] == 20);
|
||||
assert(*tbl["l"] == 30);
|
||||
assert(*tbl["s"] == 10);
|
||||
tbl["m"] = 20;
|
||||
tbl["l"] = 30;
|
||||
tbl["s"] = 10;
|
||||
assert(*tbl["m"] == 20);
|
||||
assert(*tbl["l"] == 30);
|
||||
assert(*tbl["s"] == 10);
|
||||
if(i & 1)
|
||||
tbl.remove("m");
|
||||
}
|
||||
|
||||
import std.file, std.algorithm, std.array, std.range;
|
||||
auto words = readText("/usr/share/dict/words").splitter("\n").take(1000);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import clox.value;
|
|||
import clox.vm;
|
||||
import clox.container.dynarray;
|
||||
import clox.container.table;
|
||||
import conf = clox.conf;
|
||||
|
||||
void collectGarbage(){
|
||||
debug(disableGC)
|
||||
|
|
@ -34,14 +35,23 @@ void collectGarbage(){
|
|||
markRoots();
|
||||
traceReferences();
|
||||
vm.strings.removeWhite();
|
||||
auto sweepStats = sweep();
|
||||
debug(logGC)
|
||||
printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total);
|
||||
debug(memTrace){
|
||||
import clox.memorydbg : totalGCScanned, totalGCollected, totalGCollections;
|
||||
auto sweepStats = sweep();
|
||||
totalGCollections++;
|
||||
totalGCollected += sweepStats.collected;
|
||||
totalGCScanned += sweepStats.total;
|
||||
debug(logGC)
|
||||
printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total);
|
||||
} else {
|
||||
sweep();
|
||||
}
|
||||
debug(stressGC) {} else {
|
||||
vm.nextGC = cast(size_t)(vm.bytesAllocated * 2);
|
||||
vm.nextGC = conf.nextGCGrowthFunc(vm.bytesAllocated);
|
||||
}
|
||||
}
|
||||
void mark(Obj* object){
|
||||
void mark(O)(O* o) if(is(O == Obj) || __traits(hasMember, O, "obj")){
|
||||
Obj* object = cast(Obj*)o;
|
||||
if(object is null || object.isMarked)
|
||||
return;
|
||||
debug(logGC)
|
||||
|
|
@ -53,14 +63,14 @@ void mark(Value value){
|
|||
if(value.isType(Value.Type.Obj))
|
||||
mark(value.asObj);
|
||||
}
|
||||
void mark(Table!(Obj.String*, Value) table){
|
||||
foreach(entry; table.pool){
|
||||
mark(cast(Obj*)entry.key);
|
||||
void mark(K, V)(ref Table!(K, V) table){
|
||||
foreach(ref entry; table.pool){
|
||||
mark(entry.key);
|
||||
mark(entry.val);
|
||||
}
|
||||
}
|
||||
void mark(T)(DynArray!T darray){
|
||||
foreach(item; darray[]){
|
||||
void mark(T)(ref DynArray!T darray){
|
||||
foreach(ref item; darray[]){
|
||||
mark(item);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,10 +79,10 @@ private void markRoots(){
|
|||
mark(*slot);
|
||||
}
|
||||
foreach(frame; vm.frames[0 .. vm.frameCount]){
|
||||
mark(cast(Obj*)frame.closure);
|
||||
mark(frame.closure);
|
||||
}
|
||||
for(Obj.Upvalue* upvalue = vm.openUpvalues; upvalue !is null; upvalue = upvalue.next){
|
||||
mark(cast(Obj*)upvalue);
|
||||
mark(upvalue);
|
||||
}
|
||||
mark(vm.globals);
|
||||
markCompilerRoots();
|
||||
|
|
@ -89,14 +99,23 @@ private void blacken(Obj* object){
|
|||
break;
|
||||
case Obj.Type.Function:
|
||||
Obj.Function* func = object.asFunc;
|
||||
mark(cast(Obj*)func.name);
|
||||
mark(func.name);
|
||||
mark(func.chunk.constants);
|
||||
break;
|
||||
case Obj.Type.Closure:
|
||||
Obj.Closure* closure = object.asClosure;
|
||||
mark(cast(Obj*)closure.func);
|
||||
mark(closure.func);
|
||||
foreach(upvalue; closure.upvalues)
|
||||
mark(cast(Obj*)upvalue);
|
||||
mark(upvalue);
|
||||
break;
|
||||
case Obj.Type.Class:
|
||||
Obj.Class* cls = object.asClass;
|
||||
mark(cls.name);
|
||||
break;
|
||||
case Obj.Type.Instance:
|
||||
Obj.Instance* ins = object.asInstance;
|
||||
mark(ins.cls);
|
||||
mark(ins.fields);
|
||||
break;
|
||||
case Obj.Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -108,11 +127,13 @@ private void traceReferences(){
|
|||
}
|
||||
}
|
||||
private auto sweep(){
|
||||
size_t nCollected, total;
|
||||
debug(memTrace)
|
||||
size_t nCollected, total;
|
||||
Obj* previous = null;
|
||||
Obj* object = vm.objects;
|
||||
while(object !is null){
|
||||
total++;
|
||||
debug(memTrace)
|
||||
total++;
|
||||
if(object.isMarked){
|
||||
object.isMarked = false;
|
||||
previous = object;
|
||||
|
|
@ -126,9 +147,11 @@ private auto sweep(){
|
|||
vm.objects = object;
|
||||
}
|
||||
freeObject(unreached);
|
||||
nCollected++;
|
||||
debug(memTrace)
|
||||
nCollected++;
|
||||
}
|
||||
}
|
||||
return tuple!("collected", "total")(nCollected, total);
|
||||
debug(memTrace)
|
||||
return tuple!("collected", "total")(nCollected, total);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,15 @@ void freeObject(Obj* object){
|
|||
Obj.NativeFunction* func = cast(Obj.NativeFunction*)object;
|
||||
FREE(func);
|
||||
break;
|
||||
case Obj.Type.Class:
|
||||
Obj.Class* cls = cast(Obj.Class*)object;
|
||||
FREE(cls);
|
||||
break;
|
||||
case Obj.Type.Instance:
|
||||
Obj.Instance* ins = cast(Obj.Instance*)object;
|
||||
ins.fields.free();
|
||||
FREE(ins);
|
||||
break;
|
||||
case Obj.Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,13 @@ debug(memTrace): version(D_BetterC) {} else {
|
|||
|
||||
size_t totalAllocs, totalReallocs, totalFrees;
|
||||
size_t totalAllocBytes, totalFreedBytes;
|
||||
size_t totalGCollections, totalGCScanned, totalGCollected;
|
||||
static ~this(){
|
||||
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes), Peak: %,d bytes", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes, peakMemoryUsage);
|
||||
stderr.writefln(
|
||||
colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes), Peak: %,d bytes\nTotal GCs: %,d, Collected: %,d, Scanned: %,d", Colour.Black),
|
||||
totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes, peakMemoryUsage,
|
||||
totalGCollections, totalGCollected, totalGCScanned,
|
||||
);
|
||||
if(totalAllocBytes > totalFreedBytes){
|
||||
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module clox.obj;
|
|||
|
||||
import core.stdc.stdio : printf;
|
||||
import core.stdc.string : memcpy;
|
||||
debug import std.stdio : writeln;
|
||||
|
||||
import clox.chunk;
|
||||
import clox.container.table;
|
||||
|
|
@ -12,7 +13,7 @@ import clox.gc : collectGarbage;
|
|||
|
||||
struct Obj{
|
||||
enum Type{
|
||||
None, String, Function, Closure, Upvalue, NativeFunction
|
||||
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance
|
||||
}
|
||||
Type type;
|
||||
bool isMarked;
|
||||
|
|
@ -27,6 +28,8 @@ struct Obj{
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +45,8 @@ struct Obj{
|
|||
case Type.Closure:
|
||||
Closure* closure = asClosure;
|
||||
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.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -52,6 +57,8 @@ struct Obj{
|
|||
Closure* asClosure() const pure => as!(Type.Closure);
|
||||
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
|
||||
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
|
||||
Class* asClass() const pure => as!(Type.Class);
|
||||
Instance* asInstance() const pure => as!(Type.Instance);
|
||||
|
||||
private static T* allocateObject(T)(){
|
||||
collectGarbage();
|
||||
|
|
@ -163,20 +170,46 @@ struct Obj{
|
|||
Obj obj;
|
||||
Sig func;
|
||||
static NativeFunction* create(Sig func){
|
||||
Obj.NativeFunction* native = allocateObject!(typeof(this))();
|
||||
NativeFunction* native = allocateObject!(typeof(this))();
|
||||
native.func = func;
|
||||
return native;
|
||||
}
|
||||
}
|
||||
|
||||
struct Class{
|
||||
static enum myType = Type.Class;
|
||||
Obj obj;
|
||||
String* name;
|
||||
static Class* create(String* name){
|
||||
Class* cls = allocateObject!(typeof(this))();
|
||||
cls.name = name;
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
|
||||
struct Instance{
|
||||
static enum myType = Type.Instance;
|
||||
Obj obj;
|
||||
Class* cls;
|
||||
Table!(String*, Value) fields;
|
||||
static Instance* create(Class* cls){
|
||||
Instance* ins = allocateObject!(typeof(this))();
|
||||
ins.cls = cls;
|
||||
ins.fields = typeof(ins.fields)(2);
|
||||
return ins;
|
||||
}
|
||||
}
|
||||
|
||||
bool opEquals(in ref Obj rhs) const pure{
|
||||
if(rhs.type != type)
|
||||
return false;
|
||||
final switch(type){
|
||||
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
||||
case Type.Function: return &this == &rhs;
|
||||
case Type.NativeFunction: return &this == &rhs;
|
||||
case Type.Closure: return &this == &rhs;
|
||||
case Type.Function: return &this is &rhs;
|
||||
case Type.NativeFunction: return &this is &rhs;
|
||||
case Type.Closure: return &this is &rhs;
|
||||
case Type.Class: return &this is &rhs;
|
||||
case Type.Instance: return &this is &rhs;
|
||||
case Type.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -187,6 +220,8 @@ struct Obj{
|
|||
case Type.Function: return false;
|
||||
case Type.NativeFunction: return false;
|
||||
case Type.Closure: return false;
|
||||
case Type.Class: return false;
|
||||
case Type.Instance: return false;
|
||||
case Type.Upvalue:
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
|
|
@ -201,6 +236,8 @@ struct Obj{
|
|||
case Type.NativeFunction: return "NativeFunction";
|
||||
case Type.Closure: return "Closure";
|
||||
case Type.Upvalue: return "Upvalue";
|
||||
case Type.Class: return "Class";
|
||||
case Type.Instance: return "Instance";
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,11 +69,24 @@ struct Parser{
|
|||
func();
|
||||
defineVariable(global);
|
||||
}
|
||||
void classDeclaration(){
|
||||
consume(Token.Type.Identifier, "Expect class name.");
|
||||
int nameConstant = identifierConstant(parser.previous);
|
||||
declareVariable();
|
||||
|
||||
currentCompiler.emit(OpCode.Class, VarUint(nameConstant).bytes);
|
||||
defineVariable(nameConstant);
|
||||
|
||||
consume(Token.Type.LeftBrace, "Expect '{' before class body.");
|
||||
consume(Token.Type.RightBrace, "Expect '}' after class body.");
|
||||
}
|
||||
void declaration(){
|
||||
if(match(Token.Type.Var))
|
||||
varDeclaration();
|
||||
else if(match(Token.Type.Fun))
|
||||
funDeclaration();
|
||||
else if(match(Token.Type.Class))
|
||||
classDeclaration();
|
||||
else
|
||||
statement();
|
||||
if(parser.panicMode)
|
||||
|
|
@ -369,7 +382,7 @@ struct Parser{
|
|||
while(precedence <= ParseRule.get(current.type).precedence){
|
||||
advance();
|
||||
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
||||
infixRule(currentCompiler);
|
||||
infixRule(currentCompiler, canAssign);
|
||||
}
|
||||
if(canAssign && match(Token.Type.Equal))
|
||||
error("Invalid assignment target.");
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import clox.value;
|
|||
import clox.obj;
|
||||
import clox.container.varint;
|
||||
|
||||
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);
|
||||
alias ParseFn = void function(Compiler* compiler, bool canAssign);
|
||||
|
||||
private void grouping(Compiler* compiler, bool _){
|
||||
parser.expression();
|
||||
|
|
@ -92,6 +92,17 @@ private void call(Compiler* compiler, bool _){
|
|||
VarUint argCount = VarUint(parser.argumentList());
|
||||
compiler.emit(OpCode.Call, argCount.bytes);
|
||||
}
|
||||
private void dot(Compiler* compiler, bool canAssign){
|
||||
parser.consume(Token.Type.Identifier, "Expect property name after '.'.");
|
||||
auto name = VarUint(parser.identifierConstant(parser.previous));
|
||||
|
||||
if(canAssign && parser.match(Token.Type.Equal)){
|
||||
parser.expression();
|
||||
compiler.emit(OpCode.SetProp, name.bytes);
|
||||
} else {
|
||||
compiler.emit(OpCode.GetProp, name.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ParseRule{
|
||||
|
|
@ -123,7 +134,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
|||
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Comma : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Dot : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Dot : ParseRule(null, &dot, Precedence.Call),
|
||||
Token.Type.Minus : ParseRule(&unary, &binary, Precedence.Term),
|
||||
Token.Type.Plus : ParseRule(null, &binary, Precedence.Term),
|
||||
Token.Type.Semicolon : ParseRule(null, null, Precedence.None),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module clox.vm;
|
||||
|
||||
import core.stdc.stdio;
|
||||
debug import std.stdio : writeln;
|
||||
|
||||
import clox.chunk;
|
||||
import clox.compiler;
|
||||
|
|
@ -13,20 +14,21 @@ import clox.value;
|
|||
import clox.container.dynarray;
|
||||
import clox.container.table;
|
||||
import clox.container.varint;
|
||||
import conf = clox.conf;
|
||||
|
||||
VM vm;
|
||||
|
||||
struct VM{
|
||||
Value[1024] stack;
|
||||
Value[conf.vmStackSize] stack;
|
||||
Value* stackTop;
|
||||
CallFrame[256] frames;
|
||||
CallFrame[conf.vmNCallFrames] frames;
|
||||
uint frameCount;
|
||||
Table!(Obj.String*, Value) globals;
|
||||
Table!(char[], Obj.String*) strings;
|
||||
Obj.Upvalue* openUpvalues;
|
||||
Obj* objects;
|
||||
DynArray!(Obj*) greyStack;
|
||||
size_t bytesAllocated, nextGC = 1024 * 4;
|
||||
size_t bytesAllocated, nextGC = conf.vmDefaultNextGC;
|
||||
debug(printCode) bool printCode;
|
||||
debug(traceExec) bool traceExec;
|
||||
bool isREPL, dontRun;
|
||||
|
|
@ -194,6 +196,33 @@ struct VM{
|
|||
globals[name] = peek(0);
|
||||
break;
|
||||
|
||||
case OpCode.GetProp:
|
||||
if(!peek(0).isType(Obj.Type.Instance)){
|
||||
runtimeError("Only instances have properties.");
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
Obj.Instance* ins = peek(0).asObj.asInstance;
|
||||
Obj.String* name = readString();
|
||||
Value* value = ins.fields[name];
|
||||
if(value !is null){
|
||||
pop(); // Instance
|
||||
push(*value);
|
||||
break;
|
||||
}
|
||||
runtimeError("Undefined property '%s'.", name.chars.ptr);
|
||||
return InterpretResult.RuntimeError;
|
||||
case OpCode.SetProp:
|
||||
if(!peek(1).isType(Obj.Type.Instance)){
|
||||
runtimeError("Only instances have fields.");
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
Obj.Instance* ins = peek(1).asObj.asInstance;
|
||||
ins.fields[readString()] = peek(0);
|
||||
Value value = pop();
|
||||
pop();
|
||||
push(value);
|
||||
break;
|
||||
|
||||
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
|
||||
case k:
|
||||
binaryOp!(op, true, null, Value.Type.None);
|
||||
|
|
@ -275,6 +304,9 @@ struct VM{
|
|||
push(result);
|
||||
frame = &vm.frames[vm.frameCount - 1];
|
||||
break;
|
||||
case OpCode.Class:
|
||||
push(Value.from(Obj.Class.create(readString())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(0);
|
||||
|
|
@ -334,6 +366,11 @@ struct VM{
|
|||
vm.stackTop -= argCount + 1;
|
||||
push(result);
|
||||
return true;
|
||||
case Obj.Type.Class:
|
||||
Obj.Class* cls = obj.asClass;
|
||||
vm.stackTop[-argCount - 1] = Value.from(Obj.Instance.create(cls));
|
||||
return true;
|
||||
case Obj.Type.Instance: break;
|
||||
case Obj.Type.String: break;
|
||||
case Obj.Type.Function:
|
||||
case Obj.Type.Upvalue:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ void main(){
|
|||
"./test/for.lox".match("1 2 1 2 3 ".replace(' ', '\n'));
|
||||
"./test/ifelse.lox".match("a1 b2 c3 ".replace(' ', '\n'));
|
||||
"./test/while.lox".match("1 2 3 2 1 0 1 1 2 3 2 1 0 1 ".replace(' ', '\n'));
|
||||
"./test/fields.lox".match("0 10 ".replace(' ', '\n'));
|
||||
|
||||
"./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n");
|
||||
"./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n");
|
||||
|
|
|
|||
48
test/fields.lox
Normal file
48
test/fields.lox
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
class Obj{}
|
||||
|
||||
var p = Obj();
|
||||
p.length = 0;
|
||||
p.next = nil;
|
||||
|
||||
fun append(p, item){
|
||||
p.length = p.length + 1;
|
||||
var n = Obj();
|
||||
n.next = p.next;
|
||||
p.next = n;
|
||||
}
|
||||
fun walk(p){
|
||||
class Res{}
|
||||
var n = p;
|
||||
var d = 0;
|
||||
while(n.next){
|
||||
n = n.next;
|
||||
d = d + 1;
|
||||
}
|
||||
var r = Res();
|
||||
r.item = n;
|
||||
r.depth = d;
|
||||
return r;
|
||||
}
|
||||
fun max(a, b){
|
||||
if(a > b)
|
||||
return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
var sum = 0;
|
||||
var maxLength = 0;
|
||||
|
||||
for(var i = 0; i < 10; i = i + 1){
|
||||
append(p, Obj());
|
||||
sum = sum + p.length;
|
||||
var res = walk(p);
|
||||
sum = sum - res.depth;
|
||||
maxLength = max(maxLength, p.length);
|
||||
}
|
||||
|
||||
|
||||
print sum;
|
||||
print maxLength;
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue