Closures 25

This commit is contained in:
nazrin 2025-06-11 00:30:22 +00:00
parent 1a614ac45b
commit dc4e6d33b2
16 changed files with 422 additions and 209 deletions

View file

@ -23,8 +23,10 @@ enum OpCode : ubyte{
@(OpColour("255", "200", "100")) False,
@(OpColour("000", "200", "100")) Pop,
@OpStack @(OpColour("060", "200", "150")) GetLocal,
@OpStack @(OpColour("060", "210", "150")) GetLocal,
@OpStack @(OpColour("060", "200", "150")) SetLocal,
@OpStack @(OpColour("080", "210", "150")) GetUpvalue,
@OpStack @(OpColour("080", "200", "150")) SetUpvalue,
@OpConst @(OpColour("000", "200", "150")) GetGlobal,
@OpConst @(OpColour("000", "200", "150")) DefineGlobal,
@ -51,6 +53,8 @@ enum OpCode : ubyte{
@(OpColour("000", "200", "100")) Print,
@OpCall @(OpColour("250", "200", "250")) Call,
@(OpColour("250", "200", "250")) Closure,
@(OpColour("250", "200", "050")) CloseUpvalue,
@(OpColour("250", "190", "200")) Return,
}

View file

@ -7,7 +7,7 @@ import clox.parser;
import clox.chunk;
import clox.dbg;
import clox.vm;
import clox.object;
import clox.obj;
import clox.value;
import clox.container.dynarray;
import clox.container.varint;
@ -15,17 +15,22 @@ import clox.container.varint;
Parser parser;
Compiler* currentCompiler;
struct Upvalue{
int index;
bool isLocal;
}
struct Compiler{
Compiler* enclosing;
Obj.Function* func;
enum FunctionType{ None, Function, Script }
FunctionType ftype = FunctionType.Script;
struct Local{
enum Uninitialised = -1;
Token name;
int depth;
bool isCaptured;
}
DynArray!Local locals;
DynArray!Upvalue upvalues;
int scopeDepth;
private Chunk* compilingChunk;
Chunk* currentChunk() => &func.chunk;
@ -42,7 +47,7 @@ struct Compiler{
Local local = Local();
local.depth = 0;
local.name.lexeme = "";
local.name.lexeme = "<>";
locals ~= local;
}
Obj.Function* compile(){
@ -61,10 +66,13 @@ struct Compiler{
if(!parser.hadError)
currentChunk.disassembleChunk(func.name !is null ? func.name.chars.ptr : "<script>");
}
locals.free();
currentCompiler = enclosing;
return f;
}
void free(){
locals.free();
upvalues.free();
}
void emit(Args...)(Args a){
static foreach(b; a)

View file

@ -5,7 +5,7 @@ import clox.memory;
struct DynArray(T){
private size_t _count;
private size_t _capacity;
ref size_t count() => _count;
size_t count() => _count;
size_t capacity() => _capacity;
// TODO capacity setter
T* ptr;
@ -14,7 +14,7 @@ struct DynArray(T){
_capacity = 0;
ptr = null;
}
void opOpAssign(string op: "~")(in T value){
void opOpAssign(string op: "~")(T value){
if(capacity < count + 1){
size_t oldCapacity = capacity;
_capacity = GROW_CAPACITY(oldCapacity);

View file

@ -3,12 +3,32 @@ module clox.container.table;
import core.stdc.string;
import core.stdc.stdlib;
import clox.object;
import clox.obj;
import clox.value;
import clox.memory;
enum TABLE_MAX_LOAD = 0.75;
struct StringStore{
import clox.container.dynarray;
DynArray!(Obj.String*) items;
void initialise(){
items.initialise();
}
Obj.String* find(const char[] chars, uint hash){
foreach(Obj.String* item; items){
if(item.chars == chars)
return item;
}
return null;
}
void opOpAssign(string op: "~")(Obj.String* str){
items ~= str;
}
void free(){
items.free();
}
}
enum TABLE_MAX_LOAD = 0.75;
struct Table{
size_t count, capacity;
Entry* entries;
@ -103,21 +123,6 @@ struct Table{
set(entry.key, entry.value);
}
}
Obj.String* findString(const char[] chars, uint hash){
if(count == 0)
return null;
uint index = hash % capacity;
while(true){
Entry* entry = &entries[index];
if(entry.key == null){
if(entry.value.isType(Value.Type.None))
return null;
} else if(entry.key.hash == hash && entry.key.chars == chars){
return entry.key;
}
index = (index + 1) % capacity;
}
}
}
unittest{
@ -135,8 +140,6 @@ unittest{
tbl.set(str, Value.from(true));
assert(tbl.count == 1);
assert(tbl.findString("hello", str.hash) is str);
Value val;
assert(tbl.get(str, val));
assert(val.asBoolean == true);

View file

@ -3,7 +3,6 @@ module clox.container.varint;
struct VarUint{
import std.bitmanip;
nothrow: @nogc:
uint i;
ubyte len;
private ubyte[4] data;
this(size_t l) @safe {
@ -26,27 +25,23 @@ struct VarUint{
}
assert(0);
}
static VarUint read(in ubyte* data){
VarUint v;
static int read(in ubyte* data, out ubyte len){
ubyte a = data[0];
if((data[0] & 0b1000_0000) == 0){
v.i = a;
v.len = 1;
return v;
len = 1;
return a;
}
if((a & 0b0100_0000) == 0){
ubyte[2] d = data[0 .. 2];
d[0] &= 0b0111_1111;
v.i = bigEndianToNative!ushort(d);
v.len = 2;
return v;
len = 2;
return bigEndianToNative!ushort(d);
}
if((a & 0b0010_0000) == 0){
ubyte[4] d = data[0 .. 4];
d[0] &= 0b0011_1111;
v.i = bigEndianToNative!uint(d);
v.len = 4;
return v;
len = 4;
return bigEndianToNative!uint(d);
}
assert(0);
}
@ -67,7 +62,8 @@ unittest{
ushort.max * 100
]){
auto vi = VarUint(i);
assert(i == VarUint.read(vi.bytes.ptr).i);
ubyte len;
assert(i == VarUint.read(vi.bytes.ptr, len));
}
}

View file

@ -6,6 +6,7 @@ import std.traits : EnumMembers, hasUDA, getUDAs;
import clox.chunk;
import clox.value;
import clox.util;
import clox.obj;
import clox.container.varint;
import std.stdio;
@ -16,13 +17,14 @@ private long simpleInstruction(alias OpCode op)(const char* name, long offset){
return offset + 1;
}
private long constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
VarUint constant = VarUint.read(&chunk.code[offset + 1]);
ubyte len;
int constant = VarUint.read(&chunk.code[offset + 1], len);
enum c = getUDAs!(op, OpColour)[0];
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant.i);
long pl = chunk.constants[constant.i].print();
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant);
long pl = chunk.constants[constant].print();
foreach(i; 0 .. 16-pl)
printf(" ");
return offset + 1 + constant.len;
return offset + 1 + len;
}
private long jumpInstruction(alias OpCode op)(const char* name, int sign, Chunk* chunk, long offset){
ushort jump = cast(ushort)(chunk.code[offset + 1] << 8) | chunk.code[offset + 2];
@ -34,18 +36,39 @@ private long jumpInstruction(alias OpCode op)(const char* name, int sign, Chunk*
return offset + 3;
}
private long stackIndexInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
VarUint index = VarUint.read(&chunk.code[offset + 1]);
ubyte len;
int index = VarUint.read(&chunk.code[offset + 1], len);
enum c = getUDAs!(op, OpColour)[0];
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Green)).ptr, name, index.i);
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Green)).ptr, name, index);
printf(" ");
return offset + 1 + index.len;
return offset + 1 + len;
}
private long callInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
VarUint index = VarUint.read(&chunk.code[offset + 1]);
ubyte len;
int index = VarUint.read(&chunk.code[offset + 1], len);
enum c = getUDAs!(op, OpColour)[0];
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Pink)).ptr, name, index.i);
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Pink)).ptr, name, index);
printf(" ");
return offset + 1 + index.len;
return offset + 1 + len;
}
private long closureInstruction(Chunk* chunk, long offset){
enum c = getUDAs!(OpCode.Closure, OpColour)[0];
ubyte flen;
int findex = VarUint.read(&chunk.code[++offset], flen);
offset += flen;
Obj.Function* func = chunk.constants[findex].asObj.asFunc;
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, "Closure".ptr, findex);
long pl = (cast(Obj*)(func)).print();
foreach(i; 0 .. func.upvalueCount){
bool isLocal = cast(bool)chunk.code[offset++];
ubyte len;
int index = VarUint.read(&chunk.code[offset], len);
offset += len;
pl += printf(" (%s %i)", (isLocal ? "local" : "upvalue").ptr, index);
}
foreach(i; 0 .. 18-pl)
printf(" ");
return offset ;
}
void printHeader(Chunk* chunk, const char* name = "execution"){
@ -84,6 +107,8 @@ long disassembleInstruction(Chunk* chunk, long offset){
case e: return jumpInstruction!e(e.stringof, e == OpCode.Loop ? -1 : 1, chunk, offset);
} else static if(hasUDA!(e, OpCall)){
case e: return callInstruction!e(e.stringof, chunk, offset);
} else static if(e == OpCode.Closure){
case e: return closureInstruction(chunk, offset);
} else {
case e: return simpleInstruction!e(e.stringof, offset);
}

View file

@ -50,7 +50,7 @@ int runMain(ulong argc, const char** argv){
debug(printCode) debug(traceExec) return stderr.fprintf("Usage: lox [path] [-pt]\n");
debug(printCode) return stderr.fprintf("Usage: lox [path] [-p]\n");
debug(traceExec) return stderr.fprintf("Usage: lox [path] [-t]\n");
stderr.fprintf("Usage: lox [path]\n");
stderr.fprintf("Usage: lox [-a] [path]\n");
}
const(char)[] file;
@ -60,6 +60,7 @@ int runMain(ulong argc, const char** argv){
switch(c){
debug(printCode){ case 'p': vm.printCode = true; break; }
debug(traceExec){ case 't': vm.traceExec = true; break; }
case 'a': vm.dontRun = true; break;
default:
usage();
return 64;

View file

@ -1,90 +1,17 @@
module clox.memory;
import core.stdc.stdlib;
import core.stdc.stdlib : realloc, free;
import clox.memorydbg;
import clox.obj;
import clox.util;
import clox.vm;
auto GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
auto GROW_ARRAY(T)(T* ptr, size_t oldCount, size_t newCount) => cast(T*)reallocate(ptr, T.sizeof * oldCount, T.sizeof * newCount);
auto FREE_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
auto FREE(T)(T* ptr) => reallocate(ptr, T.sizeof, 0);
auto ALLOCATE(T)(size_t count) => cast(T*)reallocate!T(null, 0, T.sizeof * count);
version(D_BetterC) {} else {
debug(memTrace): private:
import std.stdio, std.conv, std.algorithm, std.range;
import colored;
alias backtrace_state = void; // "This struct is intentionally not defined in the public interface"
alias uintptr_t = void*;
extern(C) alias backtrace_error_callback = extern(C) void function(void *data, const char *msg, int errnum) nothrow;
extern(C) backtrace_state* backtrace_create_state(const char *filename, int threaded, backtrace_error_callback error_callback, void *data) nothrow;
extern(C) extern void backtrace_print(backtrace_state *state, int skip, FILE *) nothrow;
extern(C) alias backtrace_full_callback = extern(C) int function(void *data, uintptr_t pc, const char *filename, int lineno, const char* func) nothrow;
extern(C) int backtrace_full(backtrace_state* state, int skip, backtrace_full_callback callback, backtrace_error_callback error_callback, void *data) nothrow;
backtrace_state* bts;
extern(C) void btsErrCB(void* data, const char* msg, int errnum) nothrow{
debug stderr.writeln("BT: ", msg.to!string);
}
extern(C) int btsFullCB(void* data, uintptr_t pc, const char* filename, int lineno, const char* func) nothrow{
import std.demangle;
BackTraceBuilder* btb = cast(BackTraceBuilder*)data;
if(func){
import std.string;
try{
btb.fileStack ~= filename.to!string;
btb.funcStack ~= func.to!string.demangle.stripLeft("nothrow ").stripLeft("@nogc ");
btb.lineStack ~= lineno;
} catch(Exception){
assert(0);
}
}
return 0;
}
shared static this(){
bts = backtrace_create_state(null, false, &btsErrCB, null);
assert(bts);
}
struct BackTraceBuilder{
string[] fileStack;
string[] funcStack;
int[] lineStack;
string toString() const{
import clox.util, std.format;
return zip(fileStack, lineStack, funcStack).map!(a =>
'\t' ~ colour!("%s", Colour.Yellow).format(a[0]) ~ colour!(`:`, Colour.Black) ~ colour!("%d", Colour.Cyan).format(a[1]) ~ ' ' ~ a[2]
).join("\n");
}
}
BackTraceBuilder createBacktrace(int skip = 1) nothrow{
BackTraceBuilder btb;
assert(backtrace_full(bts, skip, &btsFullCB, &btsErrCB, &btb) == 0);
return btb;
}
struct Allocation{
ulong size;
BackTraceBuilder backtrace;
}
Allocation[void*] allocatedPointers;
ulong totalAllocs, totalReallocs, totalFrees;
ulong totalAllocBytes, totalFreedBytes;
static ~this(){
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes)", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes);
if(totalAllocBytes > totalFreedBytes){
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
foreach(ptr, alloc; allocatedPointers.byPair.filter!"a[1].size"){
stderr.writeln(ptr.to!string.red, " ", alloc.size.to!string.magenta, " bytes");
stderr.writeln(alloc.backtrace);
}
}
}
}
size_t GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
T* GROW_ARRAY(T)(T* ptr, size_t oldCount, size_t newCount) => cast(T*)reallocate(ptr, T.sizeof * oldCount, T.sizeof * newCount);
T* FREE_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
T* FREE(T)(T* ptr) => reallocate(ptr, T.sizeof, 0);
T* ALLOCATE(T)(size_t count) => cast(T*)reallocate!T(null, 0, T.sizeof * count);
T* reallocate(T)(T* ptr, size_t oldSize, size_t newSize){
if(newSize == 0){ // Free
@ -118,9 +45,6 @@ T* reallocate(T)(T* ptr, size_t oldSize, size_t newSize){
return result;
}
import clox.object;
import clox.vm;
void freeObjects(){
Obj* object = vm.objects;
while(object != null){
@ -142,6 +66,16 @@ void freeObject(Obj* object){
func.chunk.free();
FREE(func);
break;
case Obj.Type.Closure:
Obj.Closure* closure = cast(Obj.Closure*)object;
if(closure.upvalues)
FREE_ARRAY(closure.upvalues.ptr, closure.upvalues.length);
FREE(closure);
break;
case Obj.Type.Upvalue:
Obj.Upvalue* upvalue = cast(Obj.Upvalue*)object;
FREE(upvalue);
break;
case Obj.Type.NativeFunction:
Obj.NativeFunction* func = cast(Obj.NativeFunction*)object;
FREE(func);

80
src/clox/memorydbg.d Normal file
View file

@ -0,0 +1,80 @@
module clox.memorydbg;
debug(memTrace): version(D_BetterC) {} else {
import std.stdio, std.conv, std.algorithm, std.range;
import colored;
import clox.util;
alias backtrace_state = void; // "This struct is intentionally not defined in the public interface"
alias uintptr_t = void*;
extern(C) alias backtrace_error_callback = extern(C) void function(void *data, const char *msg, int errnum) nothrow;
extern(C) backtrace_state* backtrace_create_state(const char *filename, int threaded, backtrace_error_callback error_callback, void *data) nothrow;
extern(C) extern void backtrace_print(backtrace_state *state, int skip, FILE *) nothrow;
extern(C) alias backtrace_full_callback = extern(C) int function(void *data, uintptr_t pc, const char *filename, int lineno, const char* func) nothrow;
extern(C) int backtrace_full(backtrace_state* state, int skip, backtrace_full_callback callback, backtrace_error_callback error_callback, void *data) nothrow;
backtrace_state* bts;
extern(C) void btsErrCB(void* data, const char* msg, int errnum) nothrow{
debug stderr.writeln("BT: ", msg.to!string);
}
extern(C) int btsFullCB(void* data, uintptr_t pc, const char* filename, int lineno, const char* func) nothrow{
import std.demangle;
BackTraceBuilder* btb = cast(BackTraceBuilder*)data;
if(func){
import std.string;
try{
btb.fileStack ~= filename.to!string;
btb.funcStack ~= func.to!string.demangle.stripLeft("nothrow ").stripLeft("@nogc ");
btb.lineStack ~= lineno;
} catch(Exception){
assert(0);
}
}
return 0;
}
shared static this(){
bts = backtrace_create_state(null, false, &btsErrCB, null);
assert(bts);
}
struct BackTraceBuilder{
string[] fileStack;
string[] funcStack;
int[] lineStack;
string toString() const{
import clox.util, std.format;
return zip(fileStack, lineStack, funcStack).map!(a =>
'\t' ~ colour!("%s", Colour.Yellow).format(a[0]) ~ colour!(`:`, Colour.Black) ~ colour!("%d", Colour.Cyan).format(a[1]) ~ ' ' ~ a[2]
).join("\n");
}
}
BackTraceBuilder createBacktrace(int skip = 1) nothrow{
BackTraceBuilder btb;
assert(backtrace_full(bts, skip, &btsFullCB, &btsErrCB, &btb) == 0);
return btb;
}
struct Allocation{
ulong size;
BackTraceBuilder backtrace;
}
Allocation[void*] allocatedPointers;
ulong totalAllocs, totalReallocs, totalFrees;
ulong totalAllocBytes, totalFreedBytes;
static ~this(){
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes)", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes);
if(totalAllocBytes > totalFreedBytes){
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
foreach(ptr, alloc; allocatedPointers.byPair.filter!"a[1].size"){
stderr.writeln(ptr.to!string.red, " ", alloc.size.to!string.magenta, " bytes");
stderr.writeln(alloc.backtrace);
}
}
}
}

View file

@ -1,6 +1,7 @@
module clox.object;
module clox.obj;
import core.stdc.stdio;
import core.stdc.stdio : printf;
import core.stdc.string : memcpy;
import clox.chunk;
import clox.container.table;
@ -10,7 +11,7 @@ import clox.vm;
struct Obj{
enum Type{
None, String, Function, NativeFunction
None, String, Function, Closure, Upvalue, NativeFunction
}
Type type;
Obj* next;
@ -22,6 +23,7 @@ struct Obj{
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.None) return &this;
}
@ -31,15 +33,20 @@ struct Obj{
case Type.Function:
Function* func = asFunc;
if(func.name !is null)
return printf("<fn %s/%d>", func.name.chars.ptr, func.arity);
return printf("<%s/%d>", func.name.chars.ptr, func.arity);
return printf("<script>");
case Type.NativeFunction: return printf("<native fn>");
case Type.NativeFunction: return printf("<native>");
case Type.Closure:
Closure* closure = asClosure;
return printf("<%s/%d>", closure.func.name.chars.ptr, closure.func.arity);
case Type.Upvalue:
case Type.None: assert(0);
}
}
String* asString() const pure => as!(Type.String);
Function* asFunc() const pure => as!(Type.Function);
Closure* asClosure() const pure => as!(Type.Closure);
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
private static T* allocateObject(T)(){
@ -60,30 +67,30 @@ struct Obj{
String* str = allocateObject!String();
str.chars = chars;
str.hash = hash;
vm.strings.set(str, Value.nil);
vm.strings ~= str;
return str;
}
static String* copy(const char[] chars){
uint strHash = hashString(chars);
Obj.String* interned = vm.strings.findString(chars, strHash);
Obj.String* interned = vm.strings.find(chars, strHash);
if(interned !is null)
return interned;
char* heapChars = ALLOCATE!char(chars.length + 1);
heapChars[0 .. chars.length] = chars;
heapChars.memcpy(chars.ptr, chars.length);
heapChars[chars.length] = '\0';
return allocateString(heapChars[0 .. chars.length], strHash);
}
static String* concat(String* a, String* b){
size_t len = a.chars.length + b.chars.length;
char* c = ALLOCATE!char(len + 1);
c[0 .. a.chars.length] = a.chars;
c[a.chars.length .. len] = b.chars;
c.memcpy(a.chars.ptr, a.chars.length);
(c + a.chars.length).memcpy(b.chars.ptr, b.chars.length);
c[len] = '\0';
return take(c[0 .. len]);
}
static Obj.String* take(char[] c) {
uint strHash = hashString(c);
Obj.String* interned = vm.strings.findString(c, strHash);
Obj.String* interned = vm.strings.find(c, strHash);
if(interned !is null){
FREE_ARRAY!char(c.ptr, c.length + 1);
return interned;
@ -95,12 +102,13 @@ struct Obj{
struct Function{
static enum myType = Type.Function;
Obj obj;
int arity;
int arity, upvalueCount;
Chunk chunk;
String* name;
static Function* create(){
Function* func = allocateObject!(typeof(this))();
func.arity = 0;
func.upvalueCount = 0;
func.chunk = Chunk.init;
func.chunk.initialise();
func.name = null;
@ -108,6 +116,39 @@ struct Obj{
}
}
struct Closure{
static enum myType = Type.Closure;
Obj obj;
Function* func;
Upvalue*[] upvalues;
static Closure* create(Function* func){
Closure* closure = allocateObject!(typeof(this))();
closure.func = func;
closure.upvalues = [];
if(closure.func.upvalueCount > 0){
closure.upvalues = ALLOCATE!(Obj.Upvalue*)(func.upvalueCount)[0 .. func.upvalueCount];
foreach(ref uv; closure.upvalues)
uv = null;
}
return closure;
}
}
struct Upvalue{
static enum myType = Type.Upvalue;
Obj obj;
Value* location;
Value closed;
Upvalue* next;
static Upvalue* create(Value* slot){
Upvalue* upvalue = allocateObject!(typeof(this))();
upvalue.location = slot;
upvalue.next = null;
upvalue.closed = Value.init;
return upvalue;
}
}
struct NativeFunction{
static enum myType = Type.NativeFunction;
alias Sig = Value function(int argCount, Value* args);
@ -127,6 +168,8 @@ struct Obj{
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.Upvalue:
case Type.None: assert(0);
}
}
@ -135,16 +178,21 @@ struct Obj{
case Type.String: return asString.chars > rhs.asString.chars;
case Type.Function: return false;
case Type.NativeFunction: return false;
case Type.Closure: return false;
case Type.Upvalue:
case Type.None: assert(0);
}
}
version(D_BetterC) {} else {
static string toString(Obj* obj){
import std.conv, std.format;
final switch(obj.type){
case Type.String: return '"' ~ obj.asString.chars.idup ~ '"';
case Type.String: return "%s".format(obj.asString.chars);
case Type.Function: return "Function";
case Type.NativeFunction: return "NativeFunction";
case Type.Closure: return "Closure";
case Type.Upvalue: return "Upvalue";
case Type.None: assert(0);
}
}

View file

@ -7,7 +7,7 @@ import clox.compiler;
import clox.chunk;
import clox.parserules;
import clox.util;
import clox.object;
import clox.obj;
import clox.value;
import clox.vm;
import clox.container.varint;
@ -196,6 +196,8 @@ struct Parser{
void func(){
Compiler compiler;
compiler.initialise(Compiler.FunctionType.Function);
scope(exit)
compiler.free();
beginScope();
consume(Token.Type.LeftParen, "Expect '(' after function name.");
@ -212,8 +214,9 @@ struct Parser{
Obj.Function* func = compiler.end();
VarUint constant = currentCompiler.makeConstant(Value.from(func));
currentCompiler.emit(OpCode.Constant, constant.bytes);
currentCompiler.emit(OpCode.Closure, VarUint(currentCompiler.makeConstant(Value.from(func))).bytes);
foreach(const ref upvalue; compiler.upvalues[])
currentCompiler.emit(upvalue.isLocal, VarUint(upvalue.index).bytes);
}
void patchJump(int offset){
@ -229,11 +232,9 @@ struct Parser{
}
void emitLoop(int loopStart){
currentCompiler.emit(OpCode.Loop);
int offset = cast(int)currentCompiler.currentChunk.code.count - loopStart + 2;
if(offset > ushort.max)
error("Loop body too large.");
currentCompiler.emit(cast(ubyte)((offset >> 8) & 0xff));
currentCompiler.emit(cast(ubyte)(offset & 0xff));
}
@ -252,10 +253,9 @@ struct Parser{
void declareVariable(){
if(currentCompiler.scopeDepth == 0)
return;
Token* name = &parser.previous;
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
currentCompiler.Local* local = &currentCompiler.locals[i];
if(local.depth != currentCompiler.Local.Uninitialised && local.depth < currentCompiler.scopeDepth)
const Token* name = &parser.previous;
foreach_reverse(const ref local; currentCompiler.locals){
if(local.depth != -1 && local.depth < currentCompiler.scopeDepth)
break;
if(name.lexeme == local.name.lexeme)
error("Already a variable with this name in this scope.");
@ -267,20 +267,24 @@ struct Parser{
declareVariable();
if(currentCompiler.scopeDepth > 0)
return 0;
return identifierConstant(&previous);
return identifierConstant(previous);
}
int identifierConstant(Token* name){
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
int identifierConstant(in ref Token name){
Obj.String* str = Obj.String.copy(name.lexeme);
Value nameVal = Value.from(str);
return currentCompiler.makeConstant(nameVal);
}
void namedVariable(Token name, bool canAssign){
void namedVariable(in ref Token name, bool canAssign){
OpCode getOp, setOp;
int arg = resolveLocal(&name);
int arg = resolveLocal(currentCompiler, name);
if(arg != -1){
getOp = OpCode.GetLocal;
setOp = OpCode.SetLocal;
} else if((arg = resolveUpvalue(currentCompiler, name)) != -1){
getOp = OpCode.GetUpvalue;
setOp = OpCode.SetUpvalue;
} else {
arg = identifierConstant(&name);
arg = identifierConstant(name);
getOp = OpCode.GetGlobal;
setOp = OpCode.SetGlobal;
}
@ -291,6 +295,56 @@ struct Parser{
currentCompiler.emit(getOp, VarUint(arg).bytes);
}
}
int resolveUpvalue(Compiler* compiler, in ref Token name){
if(compiler.enclosing is null)
return -1;
int local = resolveLocal(compiler.enclosing, name);
if(local != -1){
compiler.enclosing.locals[local].isCaptured = true;
return addUpvalue(compiler, local, true);
}
int upvalue = resolveUpvalue(compiler.enclosing, name);
if(upvalue != -1)
return addUpvalue(compiler, upvalue, false);
return -1;
}
int resolveLocal(Compiler* compiler, in ref Token name){
foreach_reverse(i, const ref local; compiler.locals){
if(name.lexeme == local.name.lexeme){
if(local.depth == -1)
error("Can't read local variable in its own initialiser.");
return cast(int)i;
}
}
return -1;
}
int addUpvalue(Compiler* compiler, in int index, in bool isLocal){
foreach(i, const ref upvalue; compiler.upvalues[]){
if(upvalue.index == index && upvalue.isLocal == isLocal){
return cast(int)i;
}
}
compiler.upvalues ~= Upvalue(index: index, isLocal: isLocal);
return compiler.func.upvalueCount++;
}
void addLocal(in ref Token name){
currentCompiler.locals ~= Compiler.Local(name, -1, false);
}
void beginScope(){
currentCompiler.scopeDepth++;
}
void endScope(){
assert(currentCompiler.scopeDepth >= 0);
currentCompiler.scopeDepth--;
while(currentCompiler.locals.count > 0 && currentCompiler.locals[currentCompiler.locals.count - 1].depth > currentCompiler.scopeDepth){
if(currentCompiler.locals[$-1].isCaptured)
currentCompiler.emit(OpCode.CloseUpvalue);
else
currentCompiler.emit(OpCode.Pop);
currentCompiler.locals.pop();
}
}
int argumentList(){
int argCount = 0;
if(!check(Token.Type.RightParen)){
@ -303,31 +357,6 @@ struct Parser{
return argCount;
}
int resolveLocal(Token* name){
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
currentCompiler.Local* local = &currentCompiler.locals[i];
if(name.lexeme == local.name.lexeme){
if(local.depth == currentCompiler.Local.Uninitialised)
error("Can't read local variable in its own initialiser.");
return i;
}
}
return -1;
}
void addLocal(Token name){
currentCompiler.locals ~= currentCompiler.Local(name, currentCompiler.Local.Uninitialised);
}
void beginScope(){
currentCompiler.scopeDepth++;
}
void endScope(){
assert(--currentCompiler.scopeDepth >= 0);
while(currentCompiler.locals.count > 0 && currentCompiler.locals[currentCompiler.locals.count - 1].depth > currentCompiler.scopeDepth){
currentCompiler.emit(OpCode.Pop);
currentCompiler.locals.count--;
}
}
void parsePrecedence(Precedence precedence){
advance();
ParseFn prefixRule = ParseRule.get(previous.type).prefix;

View file

@ -5,7 +5,7 @@ import clox.compiler;
import clox.parser;
import clox.scanner;
import clox.value;
import clox.object;
import clox.obj;
import clox.container.varint;
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);

View file

@ -3,7 +3,7 @@ module clox.value;
import core.stdc.stdio;
import clox.memory;
import clox.object;
import clox.obj;
struct Value{
enum Type{
@ -82,7 +82,7 @@ struct Value{
case Type.Bool: return asBoolean.to!string;
case Type.Nil: return "nil";
case Type.Obj: return Obj.toString(asObj);
case Type.None: assert(0);
case Type.None: return "none";
}
}
}

View file

@ -5,9 +5,8 @@ import core.stdc.stdio;
import clox.chunk;
import clox.util;
import clox.dbg;
import clox.object;
import clox.obj;
import clox.value;
import clox.object;
import clox.compiler;
import clox.scanner;
import clox.memory;
@ -22,14 +21,16 @@ struct VM{
CallFrame[256] frames;
uint frameCount;
Table globals;
Table strings;
StringStore strings;
Obj.Upvalue* openUpvalues;
Obj* objects;
bool isREPL;
debug(printCode) bool printCode;
debug(traceExec) bool traceExec;
bool dontRun;
enum InterpretResult{ Ok, CompileError, RuntimeError }
struct CallFrame{
Obj.Function* func;
Obj.Closure* closure;
ubyte* ip;
Value* slots;
}
@ -58,11 +59,18 @@ struct VM{
scanner.initialise(source);
Compiler compiler;
compiler.initialise(Compiler.FunctionType.Script);
scope(exit)
compiler.free();
Obj.Function* func = compiler.compile();
if(func is null)
return InterpretResult.CompileError;
push(Value.from(func));
call(func, 0);
Obj.Closure* closure = Obj.Closure.create(func);
pop();
push(Value.from(closure));
call(closure, 0);
if(dontRun)
return InterpretResult.Ok;
return run();
}
void runtimeError(Args...)(const char* format, Args args){
@ -73,7 +81,7 @@ struct VM{
InterpretResult run(){
CallFrame* frame = &frames[frameCount - 1];
ref ip() => frame.ip;
ref chunk() => frame.func.chunk;
ref chunk() => frame.closure.func.chunk;
debug(traceExec) if(traceExec){
printHeader(&chunk());
}
@ -83,9 +91,10 @@ struct VM{
return (ip[-2] << 8) | ip[-1];
}
int readVarUint(){
VarUint vi = VarUint.read(ip);
ip += vi.len;
return vi.i;
ubyte len;
int i = VarUint.read(ip, len);
ip += len;
return i;
}
Value readConstant() => chunk.constants[readVarUint()];
Obj.String* readString() => readConstant().asObj.asString;
@ -140,12 +149,21 @@ struct VM{
ip -= offset;
break;
case OpCode.GetUpvalue:
int slot = readVarUint();
push(*frame.closure.upvalues[slot].location);
break;
case OpCode.SetUpvalue:
int slot = readVarUint();
*frame.closure.upvalues[slot].location = peek(0);
break;
case OpCode.GetLocal:
long slot = readVarUint();
int slot = readVarUint();
push(frame.slots[slot]);
break;
case OpCode.SetLocal:
long slot = readVarUint();
int slot = readVarUint();
frame.slots[slot] = peek(0);
break;
case OpCode.GetGlobal:
@ -220,8 +238,27 @@ struct VM{
return InterpretResult.RuntimeError;
frame = &frames[frameCount - 1];
break;
case OpCode.Closure:
Obj.Function* func = readConstant().asObj.asFunc;
Obj.Closure* closure = Obj.Closure.create(func);
push(Value.from(closure));
foreach(i, ref uv; closure.upvalues){
bool isLocal = cast(bool)readByte();
int index = readVarUint();
if(isLocal){
uv = captureUpvalue(frame.slots + index);
} else {
uv = frame.closure.upvalues[index];
}
}
break;
case OpCode.CloseUpvalue:
closeUpvalues(stackTop - 1);
pop();
break;
case OpCode.Return:
Value result = pop();
closeUpvalues(frame.slots);
vm.frameCount--;
if(vm.frameCount == 0){
pop();
@ -236,7 +273,34 @@ struct VM{
assert(0);
}
bool call(Obj.Function* func, int argCount){
Obj.Upvalue* captureUpvalue(Value* local){
Obj.Upvalue* prev, upvalue = openUpvalues;
while(upvalue !is null && upvalue.location > local){
prev = upvalue;
upvalue = upvalue.next;
if(upvalue !is null && upvalue.location is local)
return upvalue;
}
Obj.Upvalue* createdUpvalue = Obj.Upvalue.create(local);
createdUpvalue.next = upvalue;
if(prev is null){
vm.openUpvalues = createdUpvalue;
} else {
prev.next = createdUpvalue;
}
return createdUpvalue;
}
void closeUpvalues(in Value* last){
while(openUpvalues !is null && openUpvalues.location >= last){
Obj.Upvalue* upvalue = vm.openUpvalues;
upvalue.closed = *upvalue.location;
upvalue.location = &upvalue.closed;
vm.openUpvalues = upvalue.next;
}
}
bool call(Obj.Closure* closure, int argCount){
Obj.Function* func = closure.func;
if(argCount != func.arity){
runtimeError("Expected %d arguments but got %d.", func.arity, argCount);
return false;
@ -246,7 +310,7 @@ struct VM{
return false;
}
CallFrame* frame = &frames[frameCount++];
frame.func = func;
frame.closure = closure;
frame.ip = func.chunk.code.ptr;
frame.slots = stackTop - argCount - 1;
return true;
@ -255,8 +319,8 @@ struct VM{
if(callee.isType(Value.Type.Obj)){
Obj* obj = callee.asObj;
final switch(obj.type){
case Obj.Type.Function:
return call(obj.asFunc, argCount);
case Obj.Type.Closure:
return call(obj.asClosure, argCount);
case Obj.Type.NativeFunction:
Obj.NativeFunction* native = obj.asNativeFunc;
Value result = native.func(argCount, vm.stackTop - argCount);
@ -264,6 +328,8 @@ struct VM{
push(result);
return true;
case Obj.Type.String: break;
case Obj.Type.Function:
case Obj.Type.Upvalue:
case Obj.Type.None: assert(0);
}
}
@ -274,7 +340,7 @@ struct VM{
void printStackTrace(){
for(int i = vm.frameCount - 1; i >= 0; i--){
CallFrame* frame = &frames[i];
Obj.Function* func = frame.func;
Obj.Function* func = frame.closure.func;
size_t instruction = frame.ip - func.chunk.code.ptr - 1;
fprintf(stderr, "[line %d] in ", func.chunk.lines[instruction]);
if(func.name is null){

View file

@ -19,11 +19,12 @@ void main(){
"./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/closure.lox".match("1\n2\n"); */
"./test/closure.lox".match("1\n2\n");
"./test/scope.lox".match("global first first second first ".replace(' ', '\n'));
"./test/fib_for.lox".match(fib(6765));
"./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/class.lox".match("The German chocolate cake is delicious!\n"); */
/* "./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n"); */

18
test/perverseclosure.lox Normal file
View file

@ -0,0 +1,18 @@
fun outer(){
var x = "value";
fun middle(){
fun inner(){
print x;
}
print "create inner closure";
return inner;
}
print "return from outer";
return middle;
}
var mid = outer();
var in = mid();
in();