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("000", "200", "000")) JumpIfFalse,
|
||||||
@OpJump @(OpColour("010", "255", "000")) Loop,
|
@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")) Equal,
|
||||||
@(OpColour("255", "100", "100")) Greater,
|
@(OpColour("255", "100", "100")) Greater,
|
||||||
@(OpColour("255", "100", "100")) Less,
|
@(OpColour("255", "100", "100")) Less,
|
||||||
|
|
@ -56,6 +59,7 @@ enum OpCode : ubyte{
|
||||||
@(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,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Chunk{
|
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);
|
assert(count > 0);
|
||||||
return ptr[--_count];
|
return ptr[--_count];
|
||||||
}
|
}
|
||||||
void reset(){
|
|
||||||
_count = 0;
|
|
||||||
debug foreach(ref item; this[])
|
|
||||||
item = T.init;
|
|
||||||
}
|
|
||||||
void free(){
|
void free(){
|
||||||
if(ptr)
|
if(ptr)
|
||||||
FREE_ARRAY!T(ptr, capacity);
|
FREE_ARRAY!T(ptr, capacity);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
module clox.container.table;
|
module clox.container.table;
|
||||||
|
|
||||||
import core.stdc.stdlib : calloc, cfree = free;
|
import core.stdc.stdlib : calloc, cfree = free;
|
||||||
import core.stdc.string;
|
|
||||||
import std.algorithm : min, max;
|
import std.algorithm : min, max;
|
||||||
import std.traits : isArray;
|
import std.traits : isArray;
|
||||||
|
debug import std.stdio : writeln;
|
||||||
|
|
||||||
import clox.memory;
|
import clox.memory;
|
||||||
import clox.memorydbg;
|
import clox.memorydbg;
|
||||||
import clox.obj;
|
import clox.obj;
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.container.dynarray;
|
import clox.container.dynarray;
|
||||||
|
import conf = clox.conf;
|
||||||
|
|
||||||
struct Table(K, V){
|
struct Table(K, V){
|
||||||
alias Hash = uint;
|
alias Hash = uint;
|
||||||
|
|
@ -21,6 +22,7 @@ struct Table(K, V){
|
||||||
bool isAlive() const => state == State.Alive;
|
bool isAlive() const => state == State.Alive;
|
||||||
bool isFree() const => state != State.Alive;
|
bool isFree() const => state != State.Alive;
|
||||||
void remove(){
|
void remove(){
|
||||||
|
this = Entry.init;
|
||||||
state = State.Dead;
|
state = State.Dead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,9 +46,11 @@ struct Table(K, V){
|
||||||
cfree(cast(void*)p.ptr);
|
cfree(cast(void*)p.ptr);
|
||||||
}
|
}
|
||||||
private void checkResize(){
|
private void checkResize(){
|
||||||
if(alive+1 < pool.length/2)
|
if(conf.tablecheckSizeFunc(alive, pool.length))
|
||||||
return;
|
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);
|
auto newTable = Table!(K, V)(newSize);
|
||||||
foreach(Entry entry; pool){
|
foreach(Entry entry; pool){
|
||||||
if(entry.isAlive)
|
if(entry.isAlive)
|
||||||
|
|
@ -81,14 +85,15 @@ struct Table(K, V){
|
||||||
Hash hash = getHash(key);
|
Hash hash = getHash(key);
|
||||||
size_t index = hash % pool.length;
|
size_t index = hash % pool.length;
|
||||||
while(true){
|
while(true){
|
||||||
const entry = pool[index];
|
const Entry* entry = &pool[index];
|
||||||
if(entry.isAlive && entry.key == key)
|
if(entry.isAlive && entry.key == key)
|
||||||
break;
|
break;
|
||||||
if(entry.isFree)
|
if(entry.isFree){
|
||||||
|
alive++;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
index = (index+1) % pool.length;
|
index = (index+1) % pool.length;
|
||||||
}
|
}
|
||||||
alive++;
|
|
||||||
pool[index] = Entry(cast(K)key, val);
|
pool[index] = Entry(cast(K)key, val);
|
||||||
}
|
}
|
||||||
V* opIndex(in K key){
|
V* opIndex(in K key){
|
||||||
|
|
@ -97,21 +102,26 @@ struct Table(K, V){
|
||||||
return &entry.val;
|
return &entry.val;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
void remove(in K key){
|
bool remove(in K key){
|
||||||
Entry* entry = find(key);
|
Entry* entry = find(key);
|
||||||
if(entry){
|
if(entry){
|
||||||
|
remove(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void remove(Entry* entry){
|
||||||
|
assert(entry);
|
||||||
entry.remove();
|
entry.remove();
|
||||||
alive--;
|
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){
|
foreach(ref entry; table.pool){
|
||||||
if(entry.state == table.State.Alive && !entry.val.obj.isMarked){
|
if(entry.state == table.State.Alive && !entry.val.obj.isMarked)
|
||||||
entry.remove();
|
table.remove(&entry);
|
||||||
table.alive--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,6 +157,7 @@ unittest{
|
||||||
assert(tbl["hello"] == null);
|
assert(tbl["hello"] == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach(i; 0 .. 3){
|
||||||
tbl.remove("l");
|
tbl.remove("l");
|
||||||
tbl["l"] = 3;
|
tbl["l"] = 3;
|
||||||
tbl["m"] = 2;
|
tbl["m"] = 2;
|
||||||
|
|
@ -161,6 +172,9 @@ unittest{
|
||||||
assert(*tbl["m"] == 20);
|
assert(*tbl["m"] == 20);
|
||||||
assert(*tbl["l"] == 30);
|
assert(*tbl["l"] == 30);
|
||||||
assert(*tbl["s"] == 10);
|
assert(*tbl["s"] == 10);
|
||||||
|
if(i & 1)
|
||||||
|
tbl.remove("m");
|
||||||
|
}
|
||||||
|
|
||||||
import std.file, std.algorithm, std.array, std.range;
|
import std.file, std.algorithm, std.array, std.range;
|
||||||
auto words = readText("/usr/share/dict/words").splitter("\n").take(1000);
|
auto words = readText("/usr/share/dict/words").splitter("\n").take(1000);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import clox.value;
|
||||||
import clox.vm;
|
import clox.vm;
|
||||||
import clox.container.dynarray;
|
import clox.container.dynarray;
|
||||||
import clox.container.table;
|
import clox.container.table;
|
||||||
|
import conf = clox.conf;
|
||||||
|
|
||||||
void collectGarbage(){
|
void collectGarbage(){
|
||||||
debug(disableGC)
|
debug(disableGC)
|
||||||
|
|
@ -34,14 +35,23 @@ void collectGarbage(){
|
||||||
markRoots();
|
markRoots();
|
||||||
traceReferences();
|
traceReferences();
|
||||||
vm.strings.removeWhite();
|
vm.strings.removeWhite();
|
||||||
|
debug(memTrace){
|
||||||
|
import clox.memorydbg : totalGCScanned, totalGCollected, totalGCollections;
|
||||||
auto sweepStats = sweep();
|
auto sweepStats = sweep();
|
||||||
|
totalGCollections++;
|
||||||
|
totalGCollected += sweepStats.collected;
|
||||||
|
totalGCScanned += sweepStats.total;
|
||||||
debug(logGC)
|
debug(logGC)
|
||||||
printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total);
|
printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total);
|
||||||
|
} else {
|
||||||
|
sweep();
|
||||||
|
}
|
||||||
debug(stressGC) {} else {
|
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)
|
if(object is null || object.isMarked)
|
||||||
return;
|
return;
|
||||||
debug(logGC)
|
debug(logGC)
|
||||||
|
|
@ -53,14 +63,14 @@ void mark(Value value){
|
||||||
if(value.isType(Value.Type.Obj))
|
if(value.isType(Value.Type.Obj))
|
||||||
mark(value.asObj);
|
mark(value.asObj);
|
||||||
}
|
}
|
||||||
void mark(Table!(Obj.String*, Value) table){
|
void mark(K, V)(ref Table!(K, V) table){
|
||||||
foreach(entry; table.pool){
|
foreach(ref entry; table.pool){
|
||||||
mark(cast(Obj*)entry.key);
|
mark(entry.key);
|
||||||
mark(entry.val);
|
mark(entry.val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void mark(T)(DynArray!T darray){
|
void mark(T)(ref DynArray!T darray){
|
||||||
foreach(item; darray[]){
|
foreach(ref item; darray[]){
|
||||||
mark(item);
|
mark(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,10 +79,10 @@ private void markRoots(){
|
||||||
mark(*slot);
|
mark(*slot);
|
||||||
}
|
}
|
||||||
foreach(frame; vm.frames[0 .. vm.frameCount]){
|
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){
|
for(Obj.Upvalue* upvalue = vm.openUpvalues; upvalue !is null; upvalue = upvalue.next){
|
||||||
mark(cast(Obj*)upvalue);
|
mark(upvalue);
|
||||||
}
|
}
|
||||||
mark(vm.globals);
|
mark(vm.globals);
|
||||||
markCompilerRoots();
|
markCompilerRoots();
|
||||||
|
|
@ -89,14 +99,23 @@ private void blacken(Obj* object){
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Function:
|
case Obj.Type.Function:
|
||||||
Obj.Function* func = object.asFunc;
|
Obj.Function* func = object.asFunc;
|
||||||
mark(cast(Obj*)func.name);
|
mark(func.name);
|
||||||
mark(func.chunk.constants);
|
mark(func.chunk.constants);
|
||||||
break;
|
break;
|
||||||
case Obj.Type.Closure:
|
case Obj.Type.Closure:
|
||||||
Obj.Closure* closure = object.asClosure;
|
Obj.Closure* closure = object.asClosure;
|
||||||
mark(cast(Obj*)closure.func);
|
mark(closure.func);
|
||||||
foreach(upvalue; closure.upvalues)
|
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;
|
break;
|
||||||
case Obj.Type.None: assert(0);
|
case Obj.Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -108,10 +127,12 @@ private void traceReferences(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private auto sweep(){
|
private auto sweep(){
|
||||||
|
debug(memTrace)
|
||||||
size_t nCollected, total;
|
size_t nCollected, total;
|
||||||
Obj* previous = null;
|
Obj* previous = null;
|
||||||
Obj* object = vm.objects;
|
Obj* object = vm.objects;
|
||||||
while(object !is null){
|
while(object !is null){
|
||||||
|
debug(memTrace)
|
||||||
total++;
|
total++;
|
||||||
if(object.isMarked){
|
if(object.isMarked){
|
||||||
object.isMarked = false;
|
object.isMarked = false;
|
||||||
|
|
@ -126,9 +147,11 @@ private auto sweep(){
|
||||||
vm.objects = object;
|
vm.objects = object;
|
||||||
}
|
}
|
||||||
freeObject(unreached);
|
freeObject(unreached);
|
||||||
|
debug(memTrace)
|
||||||
nCollected++;
|
nCollected++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
debug(memTrace)
|
||||||
return tuple!("collected", "total")(nCollected, total);
|
return tuple!("collected", "total")(nCollected, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,15 @@ void freeObject(Obj* object){
|
||||||
Obj.NativeFunction* func = cast(Obj.NativeFunction*)object;
|
Obj.NativeFunction* func = cast(Obj.NativeFunction*)object;
|
||||||
FREE(func);
|
FREE(func);
|
||||||
break;
|
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);
|
case Obj.Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,13 @@ debug(memTrace): version(D_BetterC) {} else {
|
||||||
|
|
||||||
size_t totalAllocs, totalReallocs, totalFrees;
|
size_t totalAllocs, totalReallocs, totalFrees;
|
||||||
size_t totalAllocBytes, totalFreedBytes;
|
size_t totalAllocBytes, totalFreedBytes;
|
||||||
|
size_t totalGCollections, totalGCScanned, totalGCollected;
|
||||||
static ~this(){
|
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){
|
if(totalAllocBytes > totalFreedBytes){
|
||||||
stderr.writefln("Leaked %d bytes!".lightRed.to!string, 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.stdio : printf;
|
||||||
import core.stdc.string : memcpy;
|
import core.stdc.string : memcpy;
|
||||||
|
debug import std.stdio : writeln;
|
||||||
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.container.table;
|
import clox.container.table;
|
||||||
|
|
@ -12,7 +13,7 @@ import clox.gc : collectGarbage;
|
||||||
|
|
||||||
struct Obj{
|
struct Obj{
|
||||||
enum Type{
|
enum Type{
|
||||||
None, String, Function, Closure, Upvalue, NativeFunction
|
None, String, Function, Closure, Upvalue, NativeFunction, Class, Instance
|
||||||
}
|
}
|
||||||
Type type;
|
Type type;
|
||||||
bool isMarked;
|
bool isMarked;
|
||||||
|
|
@ -27,6 +28,8 @@ struct Obj{
|
||||||
static if(type == Type.NativeFunction) return cast(NativeFunction*)&this;
|
static if(type == Type.NativeFunction) return cast(NativeFunction*)&this;
|
||||||
static if(type == Type.Closure) return cast(Closure*)&this;
|
static if(type == Type.Closure) return cast(Closure*)&this;
|
||||||
static if(type == Type.Upvalue) return cast(Upvalue*)&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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,6 +45,8 @@ struct Obj{
|
||||||
case Type.Closure:
|
case Type.Closure:
|
||||||
Closure* closure = asClosure;
|
Closure* closure = asClosure;
|
||||||
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.Instance: return printf("<instance %s>", asInstance.cls.name.chars.ptr);
|
||||||
case Type.Upvalue:
|
case Type.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +57,8 @@ struct Obj{
|
||||||
Closure* asClosure() const pure => as!(Type.Closure);
|
Closure* asClosure() const pure => as!(Type.Closure);
|
||||||
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
|
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
|
||||||
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
|
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)(){
|
private static T* allocateObject(T)(){
|
||||||
collectGarbage();
|
collectGarbage();
|
||||||
|
|
@ -163,20 +170,46 @@ struct Obj{
|
||||||
Obj obj;
|
Obj obj;
|
||||||
Sig func;
|
Sig func;
|
||||||
static NativeFunction* create(Sig func){
|
static NativeFunction* create(Sig func){
|
||||||
Obj.NativeFunction* native = allocateObject!(typeof(this))();
|
NativeFunction* native = allocateObject!(typeof(this))();
|
||||||
native.func = func;
|
native.func = func;
|
||||||
return native;
|
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{
|
bool opEquals(in ref Obj rhs) const pure{
|
||||||
if(rhs.type != type)
|
if(rhs.type != type)
|
||||||
return false;
|
return false;
|
||||||
final switch(type){
|
final switch(type){
|
||||||
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
||||||
case Type.Function: return &this == &rhs;
|
case Type.Function: return &this is &rhs;
|
||||||
case Type.NativeFunction: return &this == &rhs;
|
case Type.NativeFunction: return &this is &rhs;
|
||||||
case Type.Closure: return &this == &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.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +220,8 @@ struct Obj{
|
||||||
case Type.Function: return false;
|
case Type.Function: return false;
|
||||||
case Type.NativeFunction: return false;
|
case Type.NativeFunction: return false;
|
||||||
case Type.Closure: return false;
|
case Type.Closure: return false;
|
||||||
|
case Type.Class: return false;
|
||||||
|
case Type.Instance: return false;
|
||||||
case Type.Upvalue:
|
case Type.Upvalue:
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +236,8 @@ struct Obj{
|
||||||
case Type.NativeFunction: return "NativeFunction";
|
case Type.NativeFunction: return "NativeFunction";
|
||||||
case Type.Closure: return "Closure";
|
case Type.Closure: return "Closure";
|
||||||
case Type.Upvalue: return "Upvalue";
|
case Type.Upvalue: return "Upvalue";
|
||||||
|
case Type.Class: return "Class";
|
||||||
|
case Type.Instance: return "Instance";
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,24 @@ struct Parser{
|
||||||
func();
|
func();
|
||||||
defineVariable(global);
|
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(){
|
void declaration(){
|
||||||
if(match(Token.Type.Var))
|
if(match(Token.Type.Var))
|
||||||
varDeclaration();
|
varDeclaration();
|
||||||
else if(match(Token.Type.Fun))
|
else if(match(Token.Type.Fun))
|
||||||
funDeclaration();
|
funDeclaration();
|
||||||
|
else if(match(Token.Type.Class))
|
||||||
|
classDeclaration();
|
||||||
else
|
else
|
||||||
statement();
|
statement();
|
||||||
if(parser.panicMode)
|
if(parser.panicMode)
|
||||||
|
|
@ -369,7 +382,7 @@ struct Parser{
|
||||||
while(precedence <= ParseRule.get(current.type).precedence){
|
while(precedence <= ParseRule.get(current.type).precedence){
|
||||||
advance();
|
advance();
|
||||||
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
||||||
infixRule(currentCompiler);
|
infixRule(currentCompiler, canAssign);
|
||||||
}
|
}
|
||||||
if(canAssign && match(Token.Type.Equal))
|
if(canAssign && match(Token.Type.Equal))
|
||||||
error("Invalid assignment target.");
|
error("Invalid assignment target.");
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import clox.value;
|
||||||
import clox.obj;
|
import clox.obj;
|
||||||
import clox.container.varint;
|
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 _){
|
private void grouping(Compiler* compiler, bool _){
|
||||||
parser.expression();
|
parser.expression();
|
||||||
|
|
@ -92,6 +92,17 @@ private void call(Compiler* compiler, bool _){
|
||||||
VarUint argCount = VarUint(parser.argumentList());
|
VarUint argCount = VarUint(parser.argumentList());
|
||||||
compiler.emit(OpCode.Call, argCount.bytes);
|
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{
|
struct ParseRule{
|
||||||
|
|
@ -123,7 +134,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
||||||
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
|
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
|
||||||
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
||||||
Token.Type.Comma : 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.Minus : ParseRule(&unary, &binary, Precedence.Term),
|
||||||
Token.Type.Plus : ParseRule(null, &binary, Precedence.Term),
|
Token.Type.Plus : ParseRule(null, &binary, Precedence.Term),
|
||||||
Token.Type.Semicolon : ParseRule(null, null, Precedence.None),
|
Token.Type.Semicolon : ParseRule(null, null, Precedence.None),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
module clox.vm;
|
module clox.vm;
|
||||||
|
|
||||||
import core.stdc.stdio;
|
import core.stdc.stdio;
|
||||||
|
debug import std.stdio : writeln;
|
||||||
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.compiler;
|
import clox.compiler;
|
||||||
|
|
@ -13,20 +14,21 @@ import clox.value;
|
||||||
import clox.container.dynarray;
|
import clox.container.dynarray;
|
||||||
import clox.container.table;
|
import clox.container.table;
|
||||||
import clox.container.varint;
|
import clox.container.varint;
|
||||||
|
import conf = clox.conf;
|
||||||
|
|
||||||
VM vm;
|
VM vm;
|
||||||
|
|
||||||
struct VM{
|
struct VM{
|
||||||
Value[1024] stack;
|
Value[conf.vmStackSize] stack;
|
||||||
Value* stackTop;
|
Value* stackTop;
|
||||||
CallFrame[256] frames;
|
CallFrame[conf.vmNCallFrames] frames;
|
||||||
uint frameCount;
|
uint frameCount;
|
||||||
Table!(Obj.String*, Value) globals;
|
Table!(Obj.String*, Value) globals;
|
||||||
Table!(char[], Obj.String*) strings;
|
Table!(char[], Obj.String*) strings;
|
||||||
Obj.Upvalue* openUpvalues;
|
Obj.Upvalue* openUpvalues;
|
||||||
Obj* objects;
|
Obj* objects;
|
||||||
DynArray!(Obj*) greyStack;
|
DynArray!(Obj*) greyStack;
|
||||||
size_t bytesAllocated, nextGC = 1024 * 4;
|
size_t bytesAllocated, nextGC = conf.vmDefaultNextGC;
|
||||||
debug(printCode) bool printCode;
|
debug(printCode) bool printCode;
|
||||||
debug(traceExec) bool traceExec;
|
debug(traceExec) bool traceExec;
|
||||||
bool isREPL, dontRun;
|
bool isREPL, dontRun;
|
||||||
|
|
@ -194,6 +196,33 @@ struct VM{
|
||||||
globals[name] = peek(0);
|
globals[name] = peek(0);
|
||||||
break;
|
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: "!=" ]){
|
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
|
||||||
case k:
|
case k:
|
||||||
binaryOp!(op, true, null, Value.Type.None);
|
binaryOp!(op, true, null, Value.Type.None);
|
||||||
|
|
@ -275,6 +304,9 @@ struct VM{
|
||||||
push(result);
|
push(result);
|
||||||
frame = &vm.frames[vm.frameCount - 1];
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
break;
|
break;
|
||||||
|
case OpCode.Class:
|
||||||
|
push(Value.from(Obj.Class.create(readString())));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(0);
|
assert(0);
|
||||||
|
|
@ -334,6 +366,11 @@ struct VM{
|
||||||
vm.stackTop -= argCount + 1;
|
vm.stackTop -= argCount + 1;
|
||||||
push(result);
|
push(result);
|
||||||
return true;
|
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.String: break;
|
||||||
case Obj.Type.Function:
|
case Obj.Type.Function:
|
||||||
case Obj.Type.Upvalue:
|
case Obj.Type.Upvalue:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ void main(){
|
||||||
"./test/for.lox".match("1 2 1 2 3 ".replace(' ', '\n'));
|
"./test/for.lox".match("1 2 1 2 3 ".replace(' ', '\n'));
|
||||||
"./test/ifelse.lox".match("a1 b2 c3 ".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/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/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");
|
"./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