Closures 25
This commit is contained in:
parent
1a614ac45b
commit
dc4e6d33b2
16 changed files with 422 additions and 209 deletions
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
80
src/clox/memorydbg.d
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ¤tCompiler.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 = ¤tCompiler.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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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
18
test/perverseclosure.lox
Normal 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();
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue