Hash Tables 20
This commit is contained in:
parent
3b234814fa
commit
a7b7348f61
20 changed files with 868 additions and 837 deletions
123
src/clox/chunk.d
123
src/clox/chunk.d
|
|
@ -1,83 +1,68 @@
|
||||||
module clox.chunk;
|
module clox.chunk;
|
||||||
|
|
||||||
import std.container.array;
|
import clox.memory;
|
||||||
import std.stdio;
|
|
||||||
import std.algorithm;
|
|
||||||
|
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.object;
|
|
||||||
import clox.container.rle;
|
|
||||||
import clox.container.int24;
|
|
||||||
|
|
||||||
enum SimpleOp;
|
struct OpColour{
|
||||||
enum LogicOp;
|
string r, g, b;
|
||||||
enum ValueOp;
|
}
|
||||||
enum ArithOp;
|
|
||||||
enum CompOp;
|
enum OpCode : ubyte{
|
||||||
enum OpCode : ubyte{
|
@(OpColour("200", "200", "100")) Constant,
|
||||||
Constant,
|
@(OpColour("255", "200", "100")) Nil,
|
||||||
@ValueOp Nil,
|
@(OpColour("255", "200", "100")) True,
|
||||||
@ValueOp True,
|
@(OpColour("255", "200", "100")) False,
|
||||||
@ValueOp False,
|
|
||||||
|
@(OpColour("255", "100", "100")) Equal,
|
||||||
@(SimpleOp, CompOp) Equal,
|
@(OpColour("255", "100", "100")) Greater,
|
||||||
@(SimpleOp, CompOp) Greater,
|
@(OpColour("255", "100", "100")) Less,
|
||||||
@(SimpleOp, CompOp) Less,
|
@(OpColour("255", "100", "100")) NotEqual,
|
||||||
@(SimpleOp, CompOp) NotEqual,
|
@(OpColour("255", "100", "100")) GreaterEqual,
|
||||||
@(SimpleOp, CompOp) GreaterEqual,
|
@(OpColour("255", "100", "100")) LessEqual,
|
||||||
@(SimpleOp, CompOp) LessEqual,
|
|
||||||
|
@(OpColour("100", "100", "255")) Add,
|
||||||
@(SimpleOp, ArithOp) Add,
|
@(OpColour("100", "100", "255")) Subtract,
|
||||||
@(SimpleOp, ArithOp) Subtract,
|
@(OpColour("100", "100", "255")) Multiply,
|
||||||
@(SimpleOp, ArithOp) Multiply,
|
@(OpColour("100", "100", "255")) Divide,
|
||||||
@(SimpleOp, ArithOp) Divide,
|
|
||||||
|
@(OpColour("200", "100", "100")) Not,
|
||||||
@(SimpleOp, LogicOp) Not,
|
@(OpColour("100", "100", "200")) Negate,
|
||||||
@(SimpleOp, LogicOp) Negate,
|
@(OpColour("000", "200", "100")) Return,
|
||||||
@(SimpleOp) Return,
|
|
||||||
}
|
}
|
||||||
import std.traits: hasUDA;
|
|
||||||
bool isSimpleOp(alias op)() => hasUDA!(op, SimpleOp);
|
|
||||||
bool isValueOp(alias op)() => hasUDA!(op, ValueOp);
|
|
||||||
bool isLogicOp(alias op)() => hasUDA!(op, LogicOp);
|
|
||||||
bool isCompOp(alias op)() => hasUDA!(op, CompOp);
|
|
||||||
bool isArithOp(alias op)() => hasUDA!(op, ArithOp);
|
|
||||||
bool isSize1Op(alias op)() => isSimpleOp!op || isValueOp!op;
|
|
||||||
static assert( isSimpleOp!(OpCode.Equal) );
|
|
||||||
static assert( !isSimpleOp!(OpCode.Constant) );
|
|
||||||
|
|
||||||
struct Chunk{
|
struct Chunk{
|
||||||
Array!ubyte code;
|
uint count;
|
||||||
Rle!(Uint24, ubyte) lines;
|
uint capacity;
|
||||||
Array!Value constants;
|
ubyte* code;
|
||||||
string name;
|
uint* lines;
|
||||||
~this(){
|
ValueArray constants;
|
||||||
stderr.writeln("Deallocing chunk ", name);
|
void initialise(){
|
||||||
foreach(value; constants[].filter!isObj)
|
count = 0;
|
||||||
value.getObj.freeObject();
|
capacity = 0;
|
||||||
|
code = null;
|
||||||
|
lines = null;
|
||||||
|
constants.initialise();
|
||||||
}
|
}
|
||||||
uint addConstant(in Value value) @nogc nothrow {
|
void write(ubyte b, uint line = 0){
|
||||||
long index;
|
if(capacity < count + 1){
|
||||||
switch(value.type){
|
uint oldCapacity = capacity;
|
||||||
case Value.Type.Str:
|
capacity = GROW_CAPACITY(oldCapacity);
|
||||||
index = constants[].countUntil!(v => v.isStr && v.getStr.data == value.getStr.data);
|
code = GROW_ARRAY!ubyte(code, oldCapacity, capacity);
|
||||||
break;
|
lines = GROW_ARRAY!uint(lines, oldCapacity, capacity);
|
||||||
default:
|
|
||||||
index = constants[].countUntil(value);
|
|
||||||
}
|
}
|
||||||
if(index >= 0)
|
code[count] = b;
|
||||||
return cast(uint)index;
|
lines[count] = line;
|
||||||
constants ~= value;
|
count++;
|
||||||
return cast(uint)((constants.length) - 1);
|
|
||||||
}
|
}
|
||||||
void write(ubyte b, uint line = 0) @nogc nothrow {
|
int addConstant(Value value){
|
||||||
ubyte[1] data = [ b ];
|
constants.write(value);
|
||||||
this.write(data, line);
|
return constants.count - 1;
|
||||||
}
|
}
|
||||||
void write(ubyte[] b, uint line = 0) @nogc nothrow {
|
void free(){
|
||||||
code ~= b;
|
FREE_ARRAY!ubyte(code, capacity);
|
||||||
foreach(i; 0 .. b.length)
|
FREE_ARRAY!uint(lines, capacity);
|
||||||
lines ~= Uint24(line); // TODO could be done without a loop
|
constants.free();
|
||||||
|
initialise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,38 @@
|
||||||
module clox.compiler;
|
module clox.compiler;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdio;
|
||||||
|
|
||||||
import clox.scanner, clox.parser, clox.emitter;
|
import clox.scanner;
|
||||||
|
import clox.parser;
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.value;
|
import clox.emitter;
|
||||||
import clox.util;
|
|
||||||
import clox.parserules;
|
|
||||||
import clox.dbg;
|
import clox.dbg;
|
||||||
|
|
||||||
struct Compiler{
|
struct Compiler{
|
||||||
Scanner scanner;
|
Scanner scanner;
|
||||||
Parser parser;
|
Parser parser;
|
||||||
Emitter emitter;
|
Emitter emitter;
|
||||||
bool compile(string source, Chunk* chunk){
|
private Chunk* compilingChunk;
|
||||||
scanner = Scanner(source);
|
Chunk* currentChunk() => compilingChunk;
|
||||||
parser = Parser(&this);
|
bool compile(const(char)* source, Chunk* chunk){
|
||||||
emitter = Emitter(&this, chunk);
|
scanner.initialise(source);
|
||||||
|
parser.initialise(&this);
|
||||||
|
emitter.initialise(&this);
|
||||||
|
compilingChunk = chunk;
|
||||||
|
|
||||||
parser.advance();
|
parser.advance();
|
||||||
parser.expression();
|
parser.expression();
|
||||||
parser.consume(Token.Type.EOF, "Expect end of expression.");
|
parser.consume(Token.Type.EOF, "Expect end of expression.");
|
||||||
emitter.endCompiler();
|
end();
|
||||||
|
|
||||||
return !parser.hadError;
|
return !parser.hadError;
|
||||||
}
|
}
|
||||||
|
void end(){
|
||||||
|
emitter.emitReturn();
|
||||||
|
debug(printCode){
|
||||||
|
if(!parser.hadError)
|
||||||
|
currentChunk.disassembleChunk();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
module clox.container.int24;
|
|
||||||
|
|
||||||
struct Uint24{
|
|
||||||
nothrow: @nogc: @safe:
|
|
||||||
ubyte[3] data;
|
|
||||||
static Uint24 opCall(uint n){
|
|
||||||
import std.bitmanip : nativeToLittleEndian;
|
|
||||||
Uint24 u3;
|
|
||||||
assert(n <= 16_777_215);
|
|
||||||
ubyte[uint.sizeof] d = nativeToLittleEndian!uint(n);
|
|
||||||
u3.data[0 .. 3] = d[0 .. 3];
|
|
||||||
return u3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint toUint(Uint24 u3) @nogc nothrow @safe {
|
|
||||||
import std.bitmanip : littleEndianToNative;
|
|
||||||
ubyte[4] temp;
|
|
||||||
temp[0 .. 3] = u3.data;
|
|
||||||
return littleEndianToNative!uint(temp);
|
|
||||||
}
|
|
||||||
unittest{
|
|
||||||
static assert(Uint24.sizeof == 3);
|
|
||||||
assert(Uint24(5).toUint == 5);
|
|
||||||
assert(Uint24(16_777_215).toUint == 16_777_215);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
module clox.container.rle;
|
|
||||||
|
|
||||||
import std.traits;
|
|
||||||
import std.container.array;
|
|
||||||
|
|
||||||
struct Rle(T, L = ubyte) if(isUnsigned!L){
|
|
||||||
nothrow: @nogc:
|
|
||||||
align(1) struct Count{
|
|
||||||
T item;
|
|
||||||
L num;
|
|
||||||
}
|
|
||||||
size_t total;
|
|
||||||
Array!Count data;
|
|
||||||
private void pushNew(T item){
|
|
||||||
data ~= Count(item, 0);
|
|
||||||
}
|
|
||||||
size_t push(T item){
|
|
||||||
if(data.length){
|
|
||||||
Count* d = &data[(data.length)-1];
|
|
||||||
if(d.item == item && d.num < L.max){
|
|
||||||
d.num++;
|
|
||||||
return total++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pushNew(item);
|
|
||||||
return total++;
|
|
||||||
}
|
|
||||||
T opOpAssign(string op: "~")(T rhs){
|
|
||||||
push(rhs);
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
T opIndex(size_t n) const @safe{
|
|
||||||
assert(n < total);
|
|
||||||
size_t c;
|
|
||||||
for(size_t i; i < n;){
|
|
||||||
i += data[c].num + 1;
|
|
||||||
if(i <= n)
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
return data[c].item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unittest{
|
|
||||||
import clox.container.int24;
|
|
||||||
auto rl = Rle!(Uint24, ubyte)();
|
|
||||||
static assert(rl.Count.sizeof == 4);
|
|
||||||
foreach(i; 0..300){
|
|
||||||
size_t index = rl.push(Uint24(5));
|
|
||||||
assert(rl[index].toUint == 5);
|
|
||||||
}
|
|
||||||
assert(rl[299].toUint == 5);
|
|
||||||
foreach(i; 0..30){
|
|
||||||
size_t index = rl.push(Uint24(0));
|
|
||||||
assert(rl[index].toUint == 0);
|
|
||||||
}
|
|
||||||
assert(rl[0].toUint == 5);
|
|
||||||
foreach(i; 0..300){
|
|
||||||
size_t index = rl.push(Uint24(16_777_215));
|
|
||||||
assert(rl[index].toUint == 16_777_215);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -4,8 +4,8 @@ struct Stack(T, size_t N){
|
||||||
@nogc: nothrow:
|
@nogc: nothrow:
|
||||||
T* top;
|
T* top;
|
||||||
T[N] data;
|
T[N] data;
|
||||||
invariant{ assert(top <= data.ptr + N); assert(top >= data.ptr); }
|
/* invariant{ assert(top <= data.ptr + N); assert(top >= data.ptr); } */
|
||||||
this(int _) @safe{
|
void reset(){
|
||||||
top = data.ptr;
|
top = data.ptr;
|
||||||
}
|
}
|
||||||
void push(T value){
|
void push(T value){
|
||||||
|
|
|
||||||
176
src/clox/container/table.d
Normal file
176
src/clox/container/table.d
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
module clox.container.table;
|
||||||
|
|
||||||
|
import core.stdc.string;
|
||||||
|
import core.stdc.stdlib;
|
||||||
|
|
||||||
|
import clox.object;
|
||||||
|
import clox.value;
|
||||||
|
import clox.memory;
|
||||||
|
|
||||||
|
enum TABLE_MAX_LOAD = 0.75;
|
||||||
|
|
||||||
|
struct Table{
|
||||||
|
uint count, capacity;
|
||||||
|
Entry* entries;
|
||||||
|
struct Entry{
|
||||||
|
Obj.String* key;
|
||||||
|
Value value;
|
||||||
|
}
|
||||||
|
void initialise(){
|
||||||
|
count = capacity = 0;
|
||||||
|
entries = null;
|
||||||
|
}
|
||||||
|
void free(){
|
||||||
|
if(entries !is null)
|
||||||
|
FREE_ARRAY!Entry(entries, capacity);
|
||||||
|
initialise();
|
||||||
|
}
|
||||||
|
private void adjustCapacity(uint cap){
|
||||||
|
Entry* ent = ALLOCATE!Entry(cap);
|
||||||
|
for(int i = 0; i < cap; i++){
|
||||||
|
ent[i].key = null;
|
||||||
|
ent[i].value = Value.init;
|
||||||
|
}
|
||||||
|
count = 0;
|
||||||
|
for(int i = 0; i < capacity; i++){
|
||||||
|
Entry* entry = &entries[i];
|
||||||
|
if(entry.key is null)
|
||||||
|
continue;
|
||||||
|
Entry* dest = findEntry(entries, capacity, entry.key);
|
||||||
|
dest.key = entry.key;
|
||||||
|
dest.value = entry.value;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if(entries !is null)
|
||||||
|
FREE_ARRAY!Entry(entries, capacity);
|
||||||
|
entries = ent;
|
||||||
|
capacity = cap;
|
||||||
|
}
|
||||||
|
private Entry* findEntry(Entry* entries, int capacity, in Obj.String* key){
|
||||||
|
uint index = key.hash % capacity;
|
||||||
|
Entry* tombstone = null;
|
||||||
|
while(true){
|
||||||
|
Entry* entry = &entries[index];
|
||||||
|
if(entry.key is null){
|
||||||
|
if(entry.value.isType(Value.Type.None)){
|
||||||
|
return tombstone !is null ? tombstone : entry;
|
||||||
|
} else {
|
||||||
|
if(tombstone is null)
|
||||||
|
tombstone = entry;
|
||||||
|
}
|
||||||
|
} else if(entry.key is key){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
index = (index + 1) % capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool set(Obj.String* key, Value value){
|
||||||
|
if(count + 1 > capacity * TABLE_MAX_LOAD){
|
||||||
|
uint cap = GROW_CAPACITY(capacity);
|
||||||
|
adjustCapacity(cap);
|
||||||
|
}
|
||||||
|
Entry* entry = findEntry(entries, capacity, key);
|
||||||
|
bool isNewKey = entry.key == null;
|
||||||
|
if(isNewKey && entry.value.isType(Value.Type.None))
|
||||||
|
count++;
|
||||||
|
entry.key = key;
|
||||||
|
entry.value = value;
|
||||||
|
return isNewKey;
|
||||||
|
}
|
||||||
|
bool get(in Obj.String* key, out Value value){
|
||||||
|
if(count == 0)
|
||||||
|
return false;
|
||||||
|
Entry* entry = findEntry(entries, capacity, key);
|
||||||
|
if(entry.key is null)
|
||||||
|
return false;
|
||||||
|
value = entry.value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool del(in Obj.String* key){
|
||||||
|
if(count == 0)
|
||||||
|
return false;
|
||||||
|
Entry* entry = findEntry(entries, capacity, key);
|
||||||
|
if(entry.key is null)
|
||||||
|
return false;
|
||||||
|
entry.key = null;
|
||||||
|
entry.value = Value.from(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void addAll(Table* from){
|
||||||
|
for(int i = 0; i < from.capacity; i++){
|
||||||
|
Entry* entry = &from.entries[i];
|
||||||
|
if(entry.key !is null)
|
||||||
|
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{
|
||||||
|
Table tbl;
|
||||||
|
tbl.initialise();
|
||||||
|
scope(exit)
|
||||||
|
tbl.free();
|
||||||
|
scope(exit)
|
||||||
|
freeObjects();
|
||||||
|
|
||||||
|
assert(tbl.count == 0);
|
||||||
|
|
||||||
|
Obj.String* str = Obj.String.copy("hello");
|
||||||
|
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);
|
||||||
|
|
||||||
|
assert(tbl.del(str));
|
||||||
|
assert(tbl.get(str, val) == false);
|
||||||
|
assert(val.isType(Value.Type.None));
|
||||||
|
assert(tbl.count == 1);
|
||||||
|
|
||||||
|
foreach(i; 0..25){
|
||||||
|
Obj.String* str2 = Obj.String.copy("hello");
|
||||||
|
tbl.set(str2, Value.from(i));
|
||||||
|
assert(tbl.get(str2, val));
|
||||||
|
assert(val.asNumber == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(tbl.get(str, val) == false);
|
||||||
|
|
||||||
|
tbl.set(str, Value.from(true));
|
||||||
|
assert(tbl.get(str, val));
|
||||||
|
assert(val.asBoolean == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint jenkinsOneAtATimeHash(T)(in T[] key){
|
||||||
|
uint hash = 0;
|
||||||
|
foreach(v; key){
|
||||||
|
hash += v;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
}
|
||||||
|
hash += hash << 3;
|
||||||
|
hash ^= hash >> 11;
|
||||||
|
hash += hash << 15;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias hashString = jenkinsOneAtATimeHash;
|
||||||
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
module clox.container.varint;
|
|
||||||
|
|
||||||
struct VarUint{
|
|
||||||
import std.bitmanip;
|
|
||||||
nothrow: @nogc:
|
|
||||||
uint i;
|
|
||||||
ubyte len;
|
|
||||||
ubyte[4] data;
|
|
||||||
this(long l) @safe {
|
|
||||||
if(l < 0b1000_0000){
|
|
||||||
len = 1;
|
|
||||||
data[0] = (cast(ubyte)l);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(l < 0b0100_0000__0000_0000){
|
|
||||||
len = 2;
|
|
||||||
data[0 .. 2] = nativeToBigEndian(cast(ushort)l);
|
|
||||||
data[0] |= 0b1000_0000;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(l < 0b0010_0000__0000_0000__0000_0000__0000_0000){
|
|
||||||
len = 4;
|
|
||||||
data[0 .. 4] = nativeToBigEndian(cast(uint)l);
|
|
||||||
data[0] |= 0b1100_0000;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
static VarUint read(const(ubyte)[] data) @safe {
|
|
||||||
VarUint v;
|
|
||||||
ubyte a = data[0];
|
|
||||||
if((data[0] & 0b1000_0000) == 0){
|
|
||||||
v.i = a;
|
|
||||||
v.len = 1;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
ubyte[] bytes() @nogc nothrow {
|
|
||||||
return data[0 .. len];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unittest{
|
|
||||||
import std.range;
|
|
||||||
assert(VarUint(5).bytes.length == 1);
|
|
||||||
assert(VarUint(127).bytes.length == 1);
|
|
||||||
assert(VarUint(128).bytes.length == 2);
|
|
||||||
assert(VarUint(536_870_911).bytes.length == 4);
|
|
||||||
foreach(ulong i; [
|
|
||||||
0, 1, 2, 5,
|
|
||||||
150, 127, 128,
|
|
||||||
536_870_911,
|
|
||||||
ushort.max * 100
|
|
||||||
]){
|
|
||||||
auto vi = VarUint(i);
|
|
||||||
assert(i == VarUint.read(vi.bytes).i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
module clox.container.vartype;
|
|
||||||
|
|
||||||
import std.stdio;
|
|
||||||
import std.algorithm;
|
|
||||||
import std.array;
|
|
||||||
import std.uni;
|
|
||||||
import std.conv;
|
|
||||||
|
|
||||||
struct VarType(S) if(is(S == union)){
|
|
||||||
private enum members = __traits(derivedMembers, S);
|
|
||||||
mixin("enum Type{ None, ", [members].map!asCapitalized.join(", "), "}");
|
|
||||||
private S value;
|
|
||||||
private Type _type;
|
|
||||||
Type type() const @safe @nogc nothrow => _type;
|
|
||||||
private void check(Type t) const @safe nothrow @nogc{
|
|
||||||
assert(this.type == t, "Tried to get wrong type");
|
|
||||||
}
|
|
||||||
private template funcs(string G, string T){
|
|
||||||
mixin("bool is", G, "() const nothrow @nogc @safe => this.type == this.Type.", G, ";");
|
|
||||||
mixin("auto get", G, "() const nothrow @nogc {
|
|
||||||
check(this.Type.", G, ");
|
|
||||||
return value.", T, ";
|
|
||||||
}");
|
|
||||||
mixin("void set", G, "(typeof(S.", T, ") v = typeof(S.", T, ").init){
|
|
||||||
this._type = this.Type.", G, ";
|
|
||||||
this.value.", T, " = v;
|
|
||||||
}");
|
|
||||||
mixin("static auto ", T, "(typeof(S.", T, ") v){
|
|
||||||
typeof(this) vt; vt.set", G, "(v);
|
|
||||||
return vt;
|
|
||||||
}");
|
|
||||||
mixin("static auto ", T, "(){
|
|
||||||
typeof(this) vt;
|
|
||||||
vt.set", G, "();
|
|
||||||
return vt;
|
|
||||||
}");
|
|
||||||
}
|
|
||||||
static foreach(s; members){
|
|
||||||
mixin funcs!(s.asCapitalized.to!string, s);
|
|
||||||
}
|
|
||||||
string toString() const{
|
|
||||||
final switch(_type){
|
|
||||||
static foreach(s; members){
|
|
||||||
mixin("case Type.", s.asCapitalized.to!string, ":
|
|
||||||
return _type.to!string ~ ':' ~ get", s.asCapitalized.to!string, ".to!string ;");
|
|
||||||
}
|
|
||||||
case Type.None: return "None";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest{
|
|
||||||
import std.exception, std.stdio;
|
|
||||||
union Test{
|
|
||||||
uint u;
|
|
||||||
int i;
|
|
||||||
double d;
|
|
||||||
}
|
|
||||||
auto i = VarType!Test.i(-5);
|
|
||||||
assert(i.getI == -5);
|
|
||||||
assert(i.type == i.Type.I);
|
|
||||||
assert(i.isI);
|
|
||||||
assert(!i.isD);
|
|
||||||
i.setD(0.5);
|
|
||||||
assert(i.getD == 0.5);
|
|
||||||
assert(i.type == i.Type.D);
|
|
||||||
assert(i.isD);
|
|
||||||
assert(!i.isU);
|
|
||||||
|
|
||||||
auto i2 = VarType!Test.i();
|
|
||||||
assert(i2.isI);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,64 +1,52 @@
|
||||||
module clox.dbg;
|
module clox.dbg;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdio;
|
||||||
import std.conv;
|
import std.functional : ctEval;
|
||||||
import std.uni;
|
import std.traits : EnumMembers, hasUDA, getUDAs;
|
||||||
import std.format;
|
|
||||||
import std.meta : Filter;
|
|
||||||
import std.traits : EnumMembers;
|
|
||||||
|
|
||||||
import colored;
|
|
||||||
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.util;
|
import clox.util;
|
||||||
import clox.container.varint;
|
|
||||||
import clox.container.int24;
|
|
||||||
|
|
||||||
private ulong simpleInstruction(alias op)(string name, ulong offset){
|
private int simpleInstruction(alias OpCode op)(const char* name, int offset){
|
||||||
static if(isValueOp!op)
|
enum c = getUDAs!(op, OpColour)[0];
|
||||||
stderr.writeln(name.cyan);
|
printf(colour!("%s\n", c.r, c.g, c.b).ptr, name);
|
||||||
else static if(isLogicOp!op)
|
|
||||||
stderr.writeln(name.lightRed);
|
|
||||||
else static if(isCompOp!op)
|
|
||||||
stderr.writeln(name.red);
|
|
||||||
else static if(isArithOp!op)
|
|
||||||
stderr.writeln(name.yellow);
|
|
||||||
else
|
|
||||||
stderr.writeln(name.lightCyan);
|
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
}
|
}
|
||||||
private ulong constantInstruction(string name, Chunk* chunk, ulong offset){
|
private int constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, int offset){
|
||||||
VarUint constant = VarUint.read(chunk.code.data[offset + 1 .. $]);
|
enum c = getUDAs!(op, OpColour)[0];
|
||||||
stderr.write("%-16s".format(name).cyan, " %4d ".format(constant.i).lightMagenta, "'");
|
ubyte constant = chunk.code[offset + 1];
|
||||||
printValue(chunk.constants[constant.i]);
|
printf(ctEval!(colour!("%-16s", c.r, c.g, c.b) ~ " %4d '").ptr, name, constant);
|
||||||
stderr.writeln("'");
|
chunk.constants.values[constant].print();
|
||||||
return offset + 1 + constant.len;
|
printf("'\n");
|
||||||
|
return offset + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void disassembleChunk(Chunk* chunk, string name = "chunk"){
|
void disassembleChunk(Chunk* chunk, const char* name = "chunk"){
|
||||||
stderr.writefln("== %s ==", name);
|
printf(" == %s ==\n", name);
|
||||||
for(ulong offset = 0; offset < chunk.code.length;)
|
for(int offset = 0; offset < chunk.count;){
|
||||||
offset = disassembleInstruction(chunk, offset);
|
offset = disassembleInstruction(chunk, offset);
|
||||||
}
|
}
|
||||||
ulong disassembleInstruction(Chunk* chunk, const ulong offset){
|
}
|
||||||
stderr.write(" %04d ".format(offset).lightGray);
|
|
||||||
|
int disassembleInstruction(Chunk* chunk, int offset){
|
||||||
|
printf("%04d ", offset);
|
||||||
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
|
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
|
||||||
stderr.write(" | ".darkGray);
|
printf(colour!(" | ", Colour.Black).ptr);
|
||||||
} else {
|
} else {
|
||||||
stderr.write(" %4d ".format(chunk.lines[offset].toUint).lightGray);
|
printf(colour!("%4d ", Colour.Black).ptr, chunk.lines[offset]);
|
||||||
}
|
}
|
||||||
ubyte instruction = chunk.code[offset];
|
ubyte instruction = chunk.code[offset];
|
||||||
with(OpCode) switch(instruction){
|
switch(instruction){
|
||||||
case Constant:
|
static foreach(e; EnumMembers!OpCode){
|
||||||
return constantInstruction("OP_CONSTANT", chunk, offset);
|
static if(e == OpCode.Constant){
|
||||||
static foreach(k; Filter!(isSize1Op, EnumMembers!OpCode)){
|
case OpCode.Constant: return constantInstruction!(OpCode.Constant)("OP_CONSTANT", chunk, offset);
|
||||||
case k:
|
} else {
|
||||||
static name = "OP_" ~ (k.to!string).toUpper;
|
case e:
|
||||||
return simpleInstruction!k(name, offset);
|
return simpleInstruction!e(e.stringof, offset);
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
stderr.writefln("Unknown opcode %d", instruction);
|
default: printf("Unknown opcode %d\n", instruction);
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,31 @@
|
||||||
module clox.emitter;
|
module clox.emitter;
|
||||||
|
|
||||||
import clox.compiler;
|
import core.stdc.stdio;
|
||||||
import clox.chunk;
|
|
||||||
import clox.value;
|
|
||||||
import clox.util;
|
|
||||||
import clox.dbg;
|
|
||||||
|
|
||||||
import clox.container.varint;
|
import clox.chunk;
|
||||||
|
import clox.compiler;
|
||||||
|
import clox.value;
|
||||||
|
|
||||||
struct Emitter{
|
struct Emitter{
|
||||||
Compiler* compiler;
|
Compiler* compiler;
|
||||||
Chunk* chunk;
|
|
||||||
private uint line = 1;
|
private uint line = 1;
|
||||||
Chunk* currentChunk() @nogc nothrow {
|
uint setLine(uint line) => this.line = line;
|
||||||
return chunk;
|
void initialise(Compiler* compiler){
|
||||||
|
this.compiler = compiler;
|
||||||
}
|
}
|
||||||
void emit(Args...)(Args args) @nogc nothrow {
|
void emit(ubyte[] bytes...){
|
||||||
static foreach(v; args){{
|
foreach(b; bytes)
|
||||||
static if(is(typeof(v) == OpCode)){
|
compiler.currentChunk.write(b, compiler.parser.previous.line);
|
||||||
auto bytes = v;
|
|
||||||
} else static if(is(typeof(v) == uint)){
|
|
||||||
auto bytes = VarUint(v).bytes;
|
|
||||||
} else {
|
|
||||||
static assert(0);
|
|
||||||
}
|
}
|
||||||
currentChunk.write(bytes, line);
|
void emitReturn(){
|
||||||
}}
|
|
||||||
}
|
|
||||||
void emitConstant(Value value) @nogc nothrow {
|
|
||||||
emit(OpCode.Constant, makeConstant(value));
|
|
||||||
}
|
|
||||||
void emitReturn() @nogc nothrow {
|
|
||||||
emit(OpCode.Return);
|
emit(OpCode.Return);
|
||||||
}
|
}
|
||||||
void endCompiler(){
|
void emitConstant(Value value){
|
||||||
emitReturn();
|
emit(OpCode.Constant, cast(ubyte)makeConstant(value));
|
||||||
debug(printCode){
|
|
||||||
if(!compiler.parser.hadError)
|
|
||||||
disassembleChunk(currentChunk());
|
|
||||||
}
|
}
|
||||||
}
|
uint makeConstant(Value value){
|
||||||
uint makeConstant(Value value) @nogc nothrow {
|
uint constant = compiler.currentChunk.addConstant(value);
|
||||||
uint constant = chunk.addConstant(value);
|
|
||||||
return constant;
|
return constant;
|
||||||
}
|
}
|
||||||
void setLine(uint l) @nogc nothrow {
|
|
||||||
this.line = l;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,73 @@
|
||||||
module clox.main;
|
module clox.main;
|
||||||
|
|
||||||
import std.stdio;
|
import core.sys.posix.unistd : STDIN_FILENO;
|
||||||
import std.file;
|
import core.stdc.stdio;
|
||||||
|
import core.stdc.stdlib;
|
||||||
|
|
||||||
|
import clox.util;
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.dbg;
|
import clox.dbg;
|
||||||
import clox.vm;
|
import clox.vm;
|
||||||
import clox.object;
|
|
||||||
|
|
||||||
extern(C) int isatty(int);
|
int interpretResult(VM.InterpretResult result){
|
||||||
|
if(result == VM.InterpretResult.CompileError)
|
||||||
struct Lox{
|
return 65;
|
||||||
VM vm;
|
else if(result == VM.InterpretResult.RuntimeError)
|
||||||
this(int _){
|
return 70;
|
||||||
vm = VM(0);
|
|
||||||
}
|
|
||||||
int runFile(string path){
|
|
||||||
string source = path.readText();
|
|
||||||
VM.InterpretResult result = vm.interpret(source);
|
|
||||||
final switch(result){
|
|
||||||
case VM.InterpretResult.CompileError: return 65;
|
|
||||||
case VM.InterpretResult.RuntimeError: return 70;
|
|
||||||
case VM.InterpretResult.Ok: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int runPrompt(){
|
|
||||||
while(true){
|
|
||||||
write("lox> ");
|
|
||||||
string line = stdin.readln();
|
|
||||||
if(!line){
|
|
||||||
writeln();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
vm.interpret(line);
|
|
||||||
|
int runPrompt(){
|
||||||
|
char[1024 * 4] line;
|
||||||
|
while(true){
|
||||||
|
printf(colour!("lox> ", Colour.Green).ptr);
|
||||||
|
if(!fgets(line.ptr, line.sizeof, stdin)){
|
||||||
|
printf("\n");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
vm.interpret(line.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int runStdin(){
|
||||||
|
char* source = readStdin();
|
||||||
|
scope(exit)
|
||||||
|
source.free();
|
||||||
|
return vm.interpret(source).interpretResult;
|
||||||
|
}
|
||||||
|
int runFile(const char* path){
|
||||||
|
char* source = path.readFile();
|
||||||
|
if(!source)
|
||||||
|
return 74;
|
||||||
|
scope(exit)
|
||||||
|
source.free();
|
||||||
|
return vm.interpret(source).interpretResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
int runMain(ulong argc, const char** argv){
|
||||||
|
vm.initialise();
|
||||||
|
scope(exit)
|
||||||
|
vm.free();
|
||||||
|
|
||||||
|
if(argc == 1 && isatty(STDIN_FILENO)){
|
||||||
|
return runPrompt();
|
||||||
|
} else if(argc == 1){
|
||||||
|
return runStdin();
|
||||||
|
} else if(argc == 2){
|
||||||
|
return runFile(argv[1]);
|
||||||
|
} else {
|
||||||
|
stderr.fprintf("Usage: lox [path]\n");
|
||||||
|
return 64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(string[] argv){
|
version(D_BetterC){
|
||||||
Lox lox = Lox(0);
|
extern(C) int main(int argc, const char** argv){
|
||||||
if(isatty(stdin.fileno))
|
return runMain(argc, argv);
|
||||||
return lox.runPrompt();
|
}
|
||||||
else
|
} else {
|
||||||
return lox.runFile("/dev/stdin");
|
int main(string[] args){
|
||||||
|
import std.string, std.algorithm, std.array;
|
||||||
|
return runMain(args.length, cast(const char**)args.map!toStringz.array);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
module clox.mem;
|
module clox.memory;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdlib;
|
||||||
import std.traits;
|
|
||||||
import std.conv;
|
|
||||||
import core.stdc.stdlib : calloc, realloc, free;
|
|
||||||
import std.range, std.algorithm;
|
|
||||||
|
|
||||||
|
auto GROW_CAPACITY(uint 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;
|
import colored;
|
||||||
|
|
||||||
import clox.util;
|
|
||||||
|
|
||||||
debug {
|
|
||||||
private:
|
|
||||||
alias backtrace_state = void; // "This struct is intentionally not defined in the public interface"
|
alias backtrace_state = void; // "This struct is intentionally not defined in the public interface"
|
||||||
alias uintptr_t = void*;
|
alias uintptr_t = void*;
|
||||||
extern(C) alias backtrace_error_callback = extern(C) void function(void *data, const char *msg, int errnum) nothrow;
|
extern(C) alias backtrace_error_callback = extern(C) void function(void *data, const char *msg, int errnum) nothrow;
|
||||||
|
|
@ -35,8 +35,9 @@ debug {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static this(){
|
shared static this(){
|
||||||
bts = backtrace_create_state(null, false, &btsErrCB, null).validateAssert();
|
bts = backtrace_create_state(null, false, &btsErrCB, null);
|
||||||
|
assert(bts);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BackTraceBuilder{
|
struct BackTraceBuilder{
|
||||||
|
|
@ -48,7 +49,7 @@ debug {
|
||||||
}
|
}
|
||||||
BackTraceBuilder createBacktrace(int skip = 1) nothrow{
|
BackTraceBuilder createBacktrace(int skip = 1) nothrow{
|
||||||
BackTraceBuilder btb;
|
BackTraceBuilder btb;
|
||||||
backtrace_full(bts, skip, &btsFullCB, &btsErrCB, &btb).validateAssert!"!a";
|
assert(backtrace_full(bts, skip, &btsFullCB, &btsErrCB, &btb) == 0);
|
||||||
return btb;
|
return btb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,82 +72,61 @@ debug {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T* allocate(T)() nothrow @nogc{
|
T* reallocate(T)(T* ptr, size_t oldSize, size_t newSize){
|
||||||
return allocate!T(1).ptr;
|
if(newSize == 0){ // Free
|
||||||
}
|
|
||||||
T[] allocate(T)(size_t n) nothrow @nogc{
|
|
||||||
T* data = cast(T*)calloc(n, T.sizeof);
|
|
||||||
assert(data);
|
|
||||||
|
|
||||||
debug {
|
|
||||||
allocatedPointers[data] = Allocation(n * T.sizeof, createBacktrace());
|
|
||||||
totalAllocs++;
|
|
||||||
totalAllocBytes += n * T.sizeof;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data[0 .. n];
|
|
||||||
}
|
|
||||||
void reallocate(T)(ref T[] arr, size_t newSize) nothrow @nogc{
|
|
||||||
debug {
|
|
||||||
assert(arr.ptr, "Null pointer reallocate");
|
|
||||||
assert(arr.ptr in allocatedPointers, "Invalid ptr");
|
|
||||||
}
|
|
||||||
if(arr.length == newSize)
|
|
||||||
return;
|
|
||||||
|
|
||||||
T* newPtr = cast(T*)arr.ptr.realloc(newSize * T.sizeof);
|
|
||||||
assert(newPtr);
|
|
||||||
|
|
||||||
debug if(arr.ptr != newPtr){
|
|
||||||
totalReallocs++;
|
|
||||||
totalFreedBytes += allocatedPointers[arr.ptr].size;
|
|
||||||
totalAllocBytes += newSize;
|
|
||||||
allocatedPointers[newPtr] = Allocation(newSize);
|
|
||||||
allocatedPointers[arr.ptr] = Allocation(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(newSize > arr.length){
|
|
||||||
foreach(i; arr.length .. newSize)
|
|
||||||
newPtr[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
T[] newArr = newPtr[0 .. newSize];
|
|
||||||
arr = newArr;
|
|
||||||
}
|
|
||||||
void deallocate(T)(T* ptr) nothrow @nogc {
|
|
||||||
debug {
|
|
||||||
assert(ptr, "Null pointer free");
|
assert(ptr, "Null pointer free");
|
||||||
assert(ptr in allocatedPointers, "Invalid ptr");
|
debug(memTrace){
|
||||||
assert(allocatedPointers[ptr].size, "Double free");
|
import std.conv : to;
|
||||||
|
assert(ptr in allocatedPointers, "Invalid pointer free: " ~ ptr.to!string);
|
||||||
|
assert(allocatedPointers[ptr].size, "Double free: " ~ ptr.to!string);
|
||||||
totalFrees++;
|
totalFrees++;
|
||||||
totalFreedBytes += allocatedPointers[ptr].size;
|
totalFreedBytes += allocatedPointers[ptr].size;
|
||||||
allocatedPointers[ptr] = Allocation(0);
|
allocatedPointers[ptr] = Allocation(0);
|
||||||
}
|
}
|
||||||
ptr.free();
|
ptr.free();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
void deallocate(T)(ref T[] arr) nothrow @nogc{
|
T* result = cast(T*)ptr.realloc(newSize);
|
||||||
arr.ptr.deallocate();
|
assert(result);
|
||||||
arr = [];
|
debug(memTrace){
|
||||||
|
if(ptr is null){ // Malloc
|
||||||
|
totalAllocs++;
|
||||||
|
allocatedPointers[result] = Allocation(newSize, createBacktrace());
|
||||||
|
totalAllocBytes += newSize;
|
||||||
|
} else if(ptr !is result){ // Realloc
|
||||||
|
totalReallocs++;
|
||||||
|
totalFreedBytes += allocatedPointers[ptr].size;
|
||||||
|
totalAllocBytes += newSize;
|
||||||
|
allocatedPointers[result] = Allocation(newSize);
|
||||||
|
allocatedPointers[ptr] = Allocation(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest{
|
import clox.object;
|
||||||
int[] i = allocate!int(1);
|
import clox.vm;
|
||||||
|
|
||||||
i[0] = 5;
|
void freeObjects(){
|
||||||
|
Obj* object = vm.objects;
|
||||||
i.reallocate(64);
|
while(object != null){
|
||||||
i.reallocate(2);
|
Obj* next = object.next;
|
||||||
|
object.freeObject();
|
||||||
assert(i == [ 5, 0 ]);
|
object = next;
|
||||||
|
}
|
||||||
i.deallocate();
|
vm.objects = null;
|
||||||
|
}
|
||||||
assert(i == []);
|
void freeObject(Obj* object){
|
||||||
|
final switch(object.type){
|
||||||
int* ip = allocate!int;
|
case Obj.Type.String:
|
||||||
assert(ip);
|
Obj.String* str = cast(Obj.String*)object;
|
||||||
ip.deallocate();
|
FREE_ARRAY!char(str.chars.ptr, str.chars.length + 1);
|
||||||
|
FREE!(Obj.String)(cast(Obj.String*)object);
|
||||||
|
break;
|
||||||
|
case Obj.Type.None: assert(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
99
src/clox/obj.d
Normal file
99
src/clox/obj.d
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
module clox.object;
|
||||||
|
|
||||||
|
import core.stdc.stdio;
|
||||||
|
|
||||||
|
import clox.memory;
|
||||||
|
import clox.value;
|
||||||
|
import clox.vm;
|
||||||
|
import clox.container.table;
|
||||||
|
|
||||||
|
struct Obj{
|
||||||
|
enum Type{
|
||||||
|
None, String,
|
||||||
|
}
|
||||||
|
Type type;
|
||||||
|
Obj* next;
|
||||||
|
|
||||||
|
bool isType(Type type) const pure => this.type == type;
|
||||||
|
auto as(Type type)() const pure{
|
||||||
|
static if(type != Type.None)
|
||||||
|
assert(this.type == type);
|
||||||
|
static if(type == Type.String) return cast(String*)&this;
|
||||||
|
static if(type == Type.None) return &this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print() const{
|
||||||
|
final switch(type){
|
||||||
|
case Type.String: printf(`"%s"`, asString.chars.ptr); break;
|
||||||
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String* asString() const pure => as!(Type.String);
|
||||||
|
|
||||||
|
private static T* allocateObject(T)(){
|
||||||
|
Obj* object = reallocate!Obj(null, 0, T.sizeof);
|
||||||
|
object.type = T.myType;
|
||||||
|
object.next = vm.objects;
|
||||||
|
vm.objects = object;
|
||||||
|
return cast(T*)object;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct String{
|
||||||
|
static enum myType = Type.String;
|
||||||
|
Obj obj;
|
||||||
|
char[] chars;
|
||||||
|
uint hash;
|
||||||
|
|
||||||
|
static String* allocateString(char[] chars, uint hash){
|
||||||
|
String* str = allocateObject!String();
|
||||||
|
str.chars = chars;
|
||||||
|
str.hash = hash;
|
||||||
|
vm.strings.set(str, Value.nil);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
static String* copy(const char[] chars){
|
||||||
|
uint strHash = hashString(chars);
|
||||||
|
Obj.String* interned = vm.strings.findString(chars, strHash);
|
||||||
|
if(interned !is null)
|
||||||
|
return interned;
|
||||||
|
char* heapChars = ALLOCATE!char(chars.length + 1);
|
||||||
|
heapChars[0 .. chars.length] = chars;
|
||||||
|
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[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);
|
||||||
|
if(interned !is null){
|
||||||
|
FREE_ARRAY!char(c.ptr, c.length + 1);
|
||||||
|
return interned;
|
||||||
|
}
|
||||||
|
return allocateString(c, strHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool opEquals(in ref Obj rhs) const pure{
|
||||||
|
if(rhs.type != type)
|
||||||
|
return false;
|
||||||
|
final switch(type){
|
||||||
|
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
||||||
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool opCmp(in ref Obj rhs) const pure{
|
||||||
|
final switch(type){
|
||||||
|
case Type.String: return asString.chars > rhs.asString.chars;
|
||||||
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
module clox.object;
|
|
||||||
|
|
||||||
import std.stdio;
|
|
||||||
import std.conv;
|
|
||||||
import core.stdc.string : memcpy, memcmp, strcat;
|
|
||||||
|
|
||||||
import clox.mem;
|
|
||||||
import clox.vm;
|
|
||||||
import clox.value;
|
|
||||||
|
|
||||||
struct Obj{
|
|
||||||
enum Type{
|
|
||||||
String,
|
|
||||||
}
|
|
||||||
Type type;
|
|
||||||
Obj* next;
|
|
||||||
static T* create(T)(VM* vm = null) @nogc nothrow{
|
|
||||||
T* obj = cast(T*)allocate!T();
|
|
||||||
obj.obj.type = T.type;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
static struct String{
|
|
||||||
static enum type = Type.String;
|
|
||||||
Obj obj;
|
|
||||||
char[] data;
|
|
||||||
static String* create(size_t len, VM* vm = null) @nogc nothrow{
|
|
||||||
String* str = Obj.create!String();
|
|
||||||
if(vm){
|
|
||||||
str.obj.next = vm.objects;
|
|
||||||
vm.objects = &str.obj;
|
|
||||||
}
|
|
||||||
str.data = allocate!char(len + 1);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
static String* copy(string s, VM* vm = null) nothrow @nogc{
|
|
||||||
String* str = String.create(s.length, vm);
|
|
||||||
str.data.ptr.memcpy(s.ptr, s.length);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
static String* concat(const(String)* a, const(String)* b, VM* vm) nothrow @nogc{
|
|
||||||
String* newStr = String.create((a.data.length)-1 + (b.data.length)-1, vm);
|
|
||||||
newStr.data.ptr.memcpy(a.data.ptr, a.data.length);
|
|
||||||
newStr.data.ptr.strcat(b.data.ptr);
|
|
||||||
return newStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string toString() const{
|
|
||||||
final switch(type){
|
|
||||||
case Type.String:
|
|
||||||
const(String)* str = cast(String*)&this;
|
|
||||||
return cast(immutable)str.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isObj(Value value){
|
|
||||||
switch(value.type){
|
|
||||||
case value.Type.Str: return true;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Obj* getObj(Value value){
|
|
||||||
switch(value.type){
|
|
||||||
case value.Type.Str: return cast(Obj*)value.getStr;
|
|
||||||
default: assert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void freeObject(Obj.String* str) @nogc nothrow{
|
|
||||||
str.data.deallocate();
|
|
||||||
str.deallocate();
|
|
||||||
}
|
|
||||||
void freeObject(Obj* obj) @nogc nothrow{
|
|
||||||
final switch(obj.type){
|
|
||||||
case Obj.Type.String:
|
|
||||||
freeObject(cast(Obj.String*)obj);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,50 +1,38 @@
|
||||||
module clox.parser;
|
module clox.parser;
|
||||||
|
|
||||||
import clox.compiler;
|
import core.stdc.stdio;
|
||||||
import clox.value;
|
import std.functional : ctEval;
|
||||||
|
|
||||||
import clox.scanner;
|
import clox.scanner;
|
||||||
|
import clox.compiler;
|
||||||
|
import clox.chunk;
|
||||||
import clox.parserules;
|
import clox.parserules;
|
||||||
|
import clox.util;
|
||||||
|
|
||||||
struct Parser{
|
struct Parser{
|
||||||
Compiler* compiler;
|
Compiler* compiler;
|
||||||
Token current, previous;
|
Token current, previous;
|
||||||
bool hadError, panicMode;
|
bool hadError, panicMode;
|
||||||
void errorAtCurrent(string message){
|
void initialise(Compiler* compiler){
|
||||||
errorAt(current, message);
|
this.compiler = compiler;
|
||||||
}
|
|
||||||
void error(string message){
|
|
||||||
errorAt(previous, message);
|
|
||||||
}
|
|
||||||
void errorAt(in ref Token token, string message){
|
|
||||||
import core.stdc.stdio;
|
|
||||||
if(panicMode)
|
|
||||||
return;
|
|
||||||
panicMode = true;
|
|
||||||
fprintf(stderr, "[line %d] Error", token.line);
|
|
||||||
if(token.type == Token.Type.EOF){
|
|
||||||
fprintf(stderr, " at end");
|
|
||||||
} else if(token.type != Token.Type.Error){
|
|
||||||
fprintf(stderr, " at '%.*s'", cast(int)token.lexeme.length, token.lexeme.ptr);
|
|
||||||
}
|
|
||||||
fprintf(stderr, ": %.*s\n", cast(int)message.length, message.ptr);
|
|
||||||
hadError = true;
|
|
||||||
}
|
|
||||||
auto consume(Token.Type type, string msg){
|
|
||||||
if(current.type == type){
|
|
||||||
advance();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
errorAtCurrent(msg);
|
|
||||||
}
|
}
|
||||||
void advance(){
|
void advance(){
|
||||||
previous = current;
|
previous = current;
|
||||||
while(true){
|
while(true){
|
||||||
current = compiler.scanner.scan();
|
current = compiler.scanner.scanToken();
|
||||||
if(current.type != Token.Type.Error)
|
if(current.type != Token.Type.Error)
|
||||||
break;
|
break;
|
||||||
errorAtCurrent(current.lexeme);
|
|
||||||
|
errorAtCurrent(current.lexeme.ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void consume(Token.Type type, in char* message){
|
||||||
|
if(current.type == type){
|
||||||
|
advance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
errorAtCurrent(message);
|
||||||
|
}
|
||||||
|
|
||||||
void expression(){
|
void expression(){
|
||||||
parsePrecedence(Precedence.Assignment);
|
parsePrecedence(Precedence.Assignment);
|
||||||
|
|
@ -63,4 +51,29 @@ struct Parser{
|
||||||
infixRule(compiler);
|
infixRule(compiler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void errorAtCurrent(in char* message){
|
||||||
|
errorAt(current, message);
|
||||||
}
|
}
|
||||||
|
void error(in char* message){
|
||||||
|
errorAt(previous, message);
|
||||||
|
}
|
||||||
|
void errorAt(in ref Token token, in char* message){
|
||||||
|
if(panicMode)
|
||||||
|
return;
|
||||||
|
panicMode = true;
|
||||||
|
fprintf(stderr, ctEval!("[line %d] " ~ colour!("Error", Colour.Red)).ptr, token.line);
|
||||||
|
|
||||||
|
if (token.type == Token.Type.EOF) {
|
||||||
|
fprintf(stderr, " at end");
|
||||||
|
} else if (token.type == Token.Type.Error) {
|
||||||
|
// Nothing.
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " at '%.*s'", cast(int)token.lexeme.length, token.lexeme.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, ": %s\n", message);
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
module clox.parserules;
|
module clox.parserules;
|
||||||
|
|
||||||
import clox.compiler;
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
|
import clox.compiler;
|
||||||
|
import clox.parser;
|
||||||
import clox.scanner;
|
import clox.scanner;
|
||||||
|
import clox.emitter;
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.object;
|
import clox.object;
|
||||||
|
|
||||||
alias ParseFn = void function(Compiler* compiler);
|
alias ParseFn = void function(Compiler* compiler);
|
||||||
|
|
||||||
private void number(Compiler* compiler){
|
|
||||||
import core.stdc.stdlib : strtod;
|
|
||||||
Token token = compiler.parser.previous;
|
|
||||||
double value = strtod(token.lexeme.ptr, null);
|
|
||||||
compiler.emitter.setLine(token.line);
|
|
||||||
compiler.emitter.emitConstant(Value.num(value));
|
|
||||||
}
|
|
||||||
private void grouping(Compiler* compiler){
|
private void grouping(Compiler* compiler){
|
||||||
compiler.parser.expression();
|
compiler.parser.expression();
|
||||||
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
||||||
|
|
@ -52,6 +47,14 @@ private void binary(Compiler* compiler){
|
||||||
default: assert(0);
|
default: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void number(Compiler* compiler){
|
||||||
|
import core.stdc.stdlib : strtod;
|
||||||
|
Token token = compiler.parser.previous;
|
||||||
|
double value = strtod(token.lexeme.ptr, null);
|
||||||
|
compiler.emitter.setLine(token.line);
|
||||||
|
compiler.emitter.emitConstant(Value.from(value));
|
||||||
|
}
|
||||||
private void literal(Compiler* compiler){
|
private void literal(Compiler* compiler){
|
||||||
Token token = compiler.parser.previous;
|
Token token = compiler.parser.previous;
|
||||||
compiler.emitter.setLine(token.line);
|
compiler.emitter.setLine(token.line);
|
||||||
|
|
@ -62,19 +65,20 @@ private void literal(Compiler* compiler){
|
||||||
default: assert(0);
|
default: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void strlit(Compiler* compiler) @nogc{
|
private void strlit(Compiler* compiler){
|
||||||
Token token = compiler.parser.previous;
|
Token token = compiler.parser.previous;
|
||||||
string str = token.lexeme[1 .. $-1];
|
const char[] str = token.lexeme[1 .. $-1];
|
||||||
Obj.String* strObj = Obj.String.copy(str);
|
Obj.String* strObj = Obj.String.copy(str);
|
||||||
compiler.emitter.setLine(token.line);
|
compiler.emitter.setLine(token.line);
|
||||||
compiler.emitter.emitConstant(Value.str(strObj));
|
compiler.emitter.emitConstant(Value.from(strObj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ParseRule{
|
struct ParseRule{
|
||||||
ParseFn prefix;
|
ParseFn prefix;
|
||||||
ParseFn infix;
|
ParseFn infix;
|
||||||
Precedence precedence;
|
Precedence precedence;
|
||||||
static immutable(ParseRule)* get(Token.Type type) @nogc nothrow{
|
static immutable(ParseRule)* get(Token.Type type){
|
||||||
return &rules[type];
|
return &rules[type];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,3 +140,5 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
||||||
Token.Type.EOF : ParseRule(null, null, Precedence.None),
|
Token.Type.EOF : ParseRule(null, null, Precedence.None),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
module clox.scanner;
|
module clox.scanner;
|
||||||
|
|
||||||
import std.stdio;
|
|
||||||
import std.ascii;
|
|
||||||
|
|
||||||
import common.util;
|
|
||||||
|
|
||||||
struct Token {
|
struct Token {
|
||||||
enum Type : ubyte {
|
enum Type{
|
||||||
None, Error, EOF, // Special
|
None, Error, EOF, // Special
|
||||||
LeftParen, RightParen, // Single-character tokens.
|
LeftParen, RightParen, // Single-character tokens.
|
||||||
LeftBrace, RightBrace,
|
LeftBrace, RightBrace,
|
||||||
|
|
@ -23,70 +18,49 @@ struct Token{
|
||||||
True, Var, While,
|
True, Var, While,
|
||||||
}
|
}
|
||||||
Type type;
|
Type type;
|
||||||
int line;
|
|
||||||
string lexeme;
|
string lexeme;
|
||||||
static Token error(string msg) nothrow @nogc => Token(Token.Type.Error, 0, msg);
|
int line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isDigit(char c) => c >= '0' && c <= '9';
|
||||||
|
bool isAlpha(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||||
|
|
||||||
struct Scanner{
|
struct Scanner{
|
||||||
nothrow:
|
const(char)* start;
|
||||||
@nogc:
|
const(char)* current;
|
||||||
string start;
|
int line;
|
||||||
string current;
|
void initialise(const char* source){
|
||||||
int line = 1;
|
start = source;
|
||||||
this(string source) @nogc nothrow{
|
current = source;
|
||||||
start = current = source;
|
line = 1;
|
||||||
}
|
}
|
||||||
bool isAtEnd() const => current.length == 0;
|
char peek() const => *current;
|
||||||
private char peek() const => current[0];
|
char peekNext() const => isAtEnd ? '\0' : current[1];
|
||||||
private char peekNext() const => current.length >= 2 ? current[1] : '\0';
|
Token makeToken(Token.Type type) => Token(type, cast(string)start[0 .. current - start], line);
|
||||||
private Token makeToken(Token.Type type) const{
|
Token errorToken(string msg) => Token(Token.Type.Error, msg);
|
||||||
Token token;
|
bool isAtEnd() const => *current == '\0';
|
||||||
token.type = type;
|
char advance(){
|
||||||
token.lexeme = start[0 .. current.ptr - start.ptr];
|
current++;
|
||||||
token.line = line;
|
return current[-1];
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
private char advance(){
|
bool match(char expected){
|
||||||
char c = current[0];
|
if(isAtEnd || *current != expected)
|
||||||
current = current[1 .. $];
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
private bool match(char needle){
|
|
||||||
if(isAtEnd || current[0] != needle)
|
|
||||||
return false;
|
return false;
|
||||||
current = current[1 .. $];
|
current++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
private void skipWhitespace(){
|
Token parseString(){
|
||||||
while(!isAtEnd){
|
while(peek() != '"' && !isAtEnd){
|
||||||
char c = peek();
|
|
||||||
if(!c)
|
|
||||||
return;
|
|
||||||
if(c == '/' && peekNext() == '/'){
|
|
||||||
while(!isAtEnd && peek() != '\n')
|
|
||||||
advance();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(!c.isWhite)
|
|
||||||
return;
|
|
||||||
if(c == '\n')
|
|
||||||
line++;
|
|
||||||
current = current[1 .. $];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private Token parseString(){
|
|
||||||
while(!isAtEnd && peek() != '"'){
|
|
||||||
if(peek() == '\n')
|
if(peek() == '\n')
|
||||||
line++;
|
line++;
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
if(isAtEnd)
|
if(isAtEnd)
|
||||||
return Token.error("Unterminated string.");
|
return errorToken("Unterminated string.");
|
||||||
advance();
|
advance(); // Closing "
|
||||||
return makeToken(Token.Type.String);
|
return makeToken(Token.Type.String);
|
||||||
}
|
}
|
||||||
private Token parseNumber(){
|
Token parseNumber(){
|
||||||
while(peek().isDigit)
|
while(peek().isDigit)
|
||||||
advance();
|
advance();
|
||||||
if(peek() == '.' && peekNext().isDigit){
|
if(peek() == '.' && peekNext().isDigit){
|
||||||
|
|
@ -96,8 +70,8 @@ struct Scanner{
|
||||||
}
|
}
|
||||||
return makeToken(Token.Type.Number);
|
return makeToken(Token.Type.Number);
|
||||||
}
|
}
|
||||||
private Token parseIdentifier(){
|
Token parseIdentifier(){
|
||||||
while(peek().isAlphaNum_)
|
while(peek().isAlpha || peek().isDigit)
|
||||||
advance();
|
advance();
|
||||||
Token token = makeToken(Token.Type.Identifier);
|
Token token = makeToken(Token.Type.Identifier);
|
||||||
switch(token.lexeme){
|
switch(token.lexeme){
|
||||||
|
|
@ -121,15 +95,40 @@ struct Scanner{
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
Token scan(){
|
void skipWhitespace(){
|
||||||
|
while(true){
|
||||||
|
char c = peek();
|
||||||
|
switch(c){
|
||||||
|
case ' ', '\r':
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
line++;
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
if(peekNext() == '/'){
|
||||||
|
while(peek() != '\n' && !isAtEnd)
|
||||||
|
advance();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Token scanToken(){
|
||||||
skipWhitespace();
|
skipWhitespace();
|
||||||
start = current;
|
start = current;
|
||||||
if(isAtEnd)
|
if(isAtEnd)
|
||||||
return Token(Token.Type.EOF);
|
return Token(Token.Type.EOF);
|
||||||
char c = advance();
|
char c = advance();
|
||||||
if(c.isAlpha_)
|
|
||||||
return parseIdentifier();
|
|
||||||
switch(c){
|
switch(c){
|
||||||
|
case 'a': .. case 'z':
|
||||||
|
case 'A': .. case 'Z':
|
||||||
|
case '_': return parseIdentifier();
|
||||||
|
case '0': .. case '9': return parseNumber();
|
||||||
case '(': return makeToken(Token.Type.LeftParen);
|
case '(': return makeToken(Token.Type.LeftParen);
|
||||||
case ')': return makeToken(Token.Type.RightParen);
|
case ')': return makeToken(Token.Type.RightParen);
|
||||||
case '{': return makeToken(Token.Type.LeftBrace);
|
case '{': return makeToken(Token.Type.LeftBrace);
|
||||||
|
|
@ -148,9 +147,8 @@ struct Scanner{
|
||||||
case '"': return parseString();
|
case '"': return parseString();
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if(c.isDigit)
|
return errorToken("Unexpected character.");
|
||||||
return parseNumber();
|
|
||||||
return Token.error("Unexpected character.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,55 @@
|
||||||
module clox.util;
|
module clox.util;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdio;
|
||||||
import std.traits : isUnsigned;
|
import core.stdc.stdlib;
|
||||||
import std.container.array;
|
import std.functional : ctEval;
|
||||||
import std.functional : unaryFun;
|
|
||||||
|
|
||||||
T validateAssert(alias pred = "!!a", T)(T v, lazy string msg = null) nothrow {
|
enum Colour : string{
|
||||||
try{
|
Black = "30", Red = "31", Green = "32", Yellow = "33", Blue = "34", Pink = "35", Cyan = "36",
|
||||||
string m = msg;
|
|
||||||
static if(is(typeof(pred) == string))
|
|
||||||
m = msg ? msg : pred;
|
|
||||||
assert(v.unaryFun!pred, m);
|
|
||||||
return v;
|
|
||||||
} catch(Exception){
|
|
||||||
assert(0);
|
|
||||||
}
|
}
|
||||||
|
string colour(string text, string num)() => ctEval!("\033[1;" ~ num ~ "m" ~ text ~ "\033[0m");
|
||||||
|
string colour(string text, string r, string g, string b)() => ctEval!("\033[38;2;" ~ r ~ ";" ~ g ~ ";" ~ b ~ "m" ~ text ~ "\033[0m");
|
||||||
|
|
||||||
|
extern(C) int isatty(int);
|
||||||
|
|
||||||
|
static char* readFile(const char* path) {
|
||||||
|
FILE* file = path.fopen("rb");
|
||||||
|
if(file == null){
|
||||||
|
stderr.fprintf("Could not open file \"%s\".\n", path);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
scope(exit)
|
||||||
|
file.fclose();
|
||||||
|
|
||||||
|
file.fseek(0, SEEK_END);
|
||||||
|
size_t fileSize = file.ftell();
|
||||||
|
file.rewind();
|
||||||
|
|
||||||
|
char* buffer = cast(char*)malloc(fileSize + 1);
|
||||||
|
size_t bytesRead = fread(buffer, char.sizeof, fileSize, file);
|
||||||
|
buffer[bytesRead] = '\0';
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* readStdin(){
|
||||||
|
size_t bufferSize = 512;
|
||||||
|
char* buffer = cast(char*)malloc(bufferSize);
|
||||||
|
if (buffer == null) {
|
||||||
|
perror("Unable to allocate buffer");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
size_t totalBytesRead = 0;
|
||||||
|
size_t bytesRead;
|
||||||
|
while((bytesRead = fread(buffer + totalBytesRead, 1, bufferSize - totalBytesRead, stdin)) > 0){
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
if(totalBytesRead == bufferSize){
|
||||||
|
bufferSize *= 2;
|
||||||
|
buffer = cast(char*)realloc(buffer, bufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer[totalBytesRead] = '\0';
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
126
src/clox/value.d
126
src/clox/value.d
|
|
@ -1,51 +1,99 @@
|
||||||
module clox.value;
|
module clox.value;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdio;
|
||||||
import std.conv;
|
|
||||||
|
|
||||||
import colored;
|
|
||||||
|
|
||||||
|
import clox.memory;
|
||||||
import clox.object;
|
import clox.object;
|
||||||
import clox.container.vartype;
|
|
||||||
|
|
||||||
private union U{
|
struct Value{
|
||||||
bool bln;
|
enum Type{
|
||||||
bool nil;
|
None, Bool, Nil, Number, Obj
|
||||||
double num;
|
|
||||||
Obj.String* str;
|
|
||||||
}
|
}
|
||||||
alias Value = VarType!U;
|
Type type;
|
||||||
|
private union Un{
|
||||||
|
bool boolean;
|
||||||
|
double number;
|
||||||
|
Obj* obj;
|
||||||
|
}
|
||||||
|
private Un un;
|
||||||
|
|
||||||
void printValue(Value value){
|
bool isType(Type type) const pure => this.type == type;
|
||||||
final switch(value.type){
|
bool isType(Obj.Type type) const pure => this.type == Type.Obj && asObj.isType(type);
|
||||||
case value.Type.Bln: stderr.writef("%s", value.getBln.to!string.yellow); break;
|
auto as(Type type)() const pure{
|
||||||
case value.Type.Num: stderr.writef("%g", value.getNum); break;
|
static if(type != Type.None)
|
||||||
case value.Type.Nil: stderr.writef("nil"); break;
|
assert(this.type == type);
|
||||||
case value.Type.Str: stderr.writef("%s", value.getStr.data); break;
|
static if(type == Type.Number) return un.number;
|
||||||
case value.Type.None: assert(0);
|
static if(type == Type.Bool) return un.boolean;
|
||||||
|
static if(type == Type.Obj) return un.obj;
|
||||||
|
static if(type == Type.None) return this;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
bool isTruthy(Value value) nothrow @nogc {
|
bool asBoolean() const pure => as!(Type.Bool);
|
||||||
final switch(value.type){
|
double asNumber() const pure => as!(Type.Number);
|
||||||
case value.Type.Bln: return value.getBln;
|
Obj* asObj() const pure => cast(Obj*)as!(Type.Obj);
|
||||||
case value.Type.Num: return true;
|
|
||||||
case value.Type.Nil: return false;
|
static Value from(T: double)(T val) => Value(Type.Number, Un(number: val));
|
||||||
case value.Type.Str: return true;
|
static Value from(T: bool)(T val) => Value(Type.Bool, Un(boolean: val));
|
||||||
case value.Type.None: assert(0);
|
static Value from(T)(T* val) if(__traits(compiles, val.obj)) => Value(Type.Obj, Un(obj: cast(Obj*)val));
|
||||||
}
|
static Value from(T: Value)(T val) => val;
|
||||||
}
|
static Value nil() pure => Value(Type.Nil);
|
||||||
bool isFalsey(Value value) nothrow @nogc {
|
|
||||||
return !isTruthy(value);
|
bool isFalsey() const pure => (type == Type.Bool && asBoolean == false) || type == Type.Nil;
|
||||||
}
|
bool isTruthy() const pure => !isFalsey;
|
||||||
bool compare(string op)(Value a, Value b){
|
bool opEquals(Value rhs) const pure{
|
||||||
if(a.type != b.type)
|
if(rhs.type != type)
|
||||||
return false;
|
return false;
|
||||||
final switch(a.type){
|
final switch(type){
|
||||||
case a.Type.Bln: return mixin("a.getBln", op, "b.getBln");
|
case Type.Number: return asNumber == rhs.asNumber;
|
||||||
case a.Type.Num: return mixin("a.getNum", op, "b.getNum");
|
case Type.Bool: return asBoolean == rhs.asBoolean;
|
||||||
case a.Type.Str: return mixin("a.getStr.data", op, "b.getStr.data");
|
case Type.Nil: return true;
|
||||||
case a.Type.Nil: return true;
|
case Type.Obj: return *asObj == *rhs.asObj;
|
||||||
case a.Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int opCmp(Value rhs) const pure{
|
||||||
|
final switch(type){
|
||||||
|
case Type.Number: return asNumber > rhs.asNumber;
|
||||||
|
case Type.Bool: return asBoolean > rhs.asBoolean;
|
||||||
|
case Type.Nil: return 0;
|
||||||
|
case Type.Obj: return asObj > rhs.asObj;
|
||||||
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void print() const{
|
||||||
|
final switch(type){
|
||||||
|
case Type.Number: printf("%g", asNumber); break;
|
||||||
|
case Type.Bool: printf(asBoolean ? "true" : "false"); break;
|
||||||
|
case Type.Nil: printf("nil"); break;
|
||||||
|
case Type.Obj: asObj.print(); break;
|
||||||
|
case Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ValueArray{
|
||||||
|
uint count;
|
||||||
|
uint capacity;
|
||||||
|
Value* values;
|
||||||
|
void initialise(){
|
||||||
|
count = 0;
|
||||||
|
capacity = 0;
|
||||||
|
values = null;
|
||||||
|
}
|
||||||
|
void write(Value value){
|
||||||
|
if(capacity < count + 1){
|
||||||
|
int oldCapacity = capacity;
|
||||||
|
capacity = GROW_CAPACITY(oldCapacity);
|
||||||
|
values = GROW_ARRAY!Value(values, oldCapacity, capacity);
|
||||||
|
}
|
||||||
|
values[count] = value;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
void free(){
|
||||||
|
if(values)
|
||||||
|
FREE_ARRAY!Value(values, capacity);
|
||||||
|
initialise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
172
src/clox/vm.d
172
src/clox/vm.d
|
|
@ -1,135 +1,137 @@
|
||||||
module clox.vm;
|
module clox.vm;
|
||||||
|
|
||||||
import std.stdio;
|
import core.stdc.stdio;
|
||||||
|
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.value;
|
|
||||||
import clox.dbg;
|
import clox.dbg;
|
||||||
import clox.util;
|
|
||||||
import clox.compiler;
|
|
||||||
import clox.object;
|
import clox.object;
|
||||||
|
import clox.value;
|
||||||
|
import clox.object;
|
||||||
|
import clox.compiler;
|
||||||
|
import clox.memory;
|
||||||
import clox.container.stack;
|
import clox.container.stack;
|
||||||
import clox.container.varint;
|
import clox.container.table;
|
||||||
import clox.container.int24;
|
|
||||||
|
|
||||||
enum stackMax = 256;
|
VM vm;
|
||||||
|
|
||||||
struct VM{
|
struct VM{
|
||||||
const(ubyte)* ip;
|
|
||||||
Stack!(Value, stackMax) stack;
|
|
||||||
Chunk* chunk;
|
Chunk* chunk;
|
||||||
|
ubyte* ip;
|
||||||
|
Stack!(Value, 256) stack;
|
||||||
|
Table strings;
|
||||||
Obj* objects;
|
Obj* objects;
|
||||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||||
this(int _) @nogc nothrow {
|
void initialise(){
|
||||||
stack = typeof(stack)(0);
|
stack.reset();
|
||||||
|
strings.initialise();
|
||||||
}
|
}
|
||||||
~this(){
|
void free(){
|
||||||
freeObjects();
|
freeObjects();
|
||||||
|
strings.free();
|
||||||
}
|
}
|
||||||
InterpretResult interpret(string source){
|
InterpretResult interpret(const char* source){
|
||||||
Chunk c = Chunk();
|
Chunk cnk;
|
||||||
|
cnk.initialise();
|
||||||
|
scope(exit)
|
||||||
|
cnk.free();
|
||||||
Compiler compiler;
|
Compiler compiler;
|
||||||
if(!compiler.compile(source, &c))
|
if(!compiler.compile(source, &cnk))
|
||||||
return InterpretResult.CompileError;
|
return InterpretResult.CompileError;
|
||||||
chunk = &c;
|
this.chunk = &cnk;
|
||||||
return interpret(chunk);
|
this.ip = cnk.code;
|
||||||
}
|
|
||||||
InterpretResult interpret(Chunk* chunk){
|
|
||||||
this.chunk = chunk;
|
|
||||||
ip = &chunk.code[0];
|
|
||||||
return run();
|
return run();
|
||||||
}
|
}
|
||||||
private void runtimeError(Args...)(string format, Args args) nothrow {
|
void runtimeError(Args...)(const char* format, Args args){
|
||||||
size_t instruction = ip - (&chunk.code[0]) - 1;
|
fprintf(stderr, format, args);
|
||||||
uint line = chunk.lines[instruction].toUint;
|
fputs("\n", stderr);
|
||||||
try{
|
|
||||||
stderr.writef("[line %d] ", line);
|
size_t instruction = vm.ip - vm.chunk.code - 1;
|
||||||
stderr.writefln(format, args);
|
int line = vm.chunk.lines[instruction];
|
||||||
} catch(Exception){}
|
fprintf(stderr, "[line %d] in script\n", line);
|
||||||
/* stack.reset(); */
|
stack.reset();
|
||||||
}
|
}
|
||||||
private InterpretResult run() nothrow {
|
InterpretResult run(){
|
||||||
auto readByte() => *ip++;
|
ubyte readByte() => *vm.ip++;
|
||||||
auto readIns() => cast(OpCode)readByte();
|
auto readConstant() => chunk.constants.values[readByte()];
|
||||||
Value readConstant(){
|
Value peek(int distance) => stack.top[-1 - distance];
|
||||||
VarUint constant = VarUint.read(ip[0 .. 4]);
|
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
||||||
ip += constant.len;
|
bool checkSameType()() => peek(0).type == peek(1).type;
|
||||||
return chunk.constants[constant.i];
|
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||||
|
if(!check){
|
||||||
|
runtimeError(checkMsg.ptr);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
Value peek(int distance = 0){
|
auto b = stack.pop().as!pre;
|
||||||
return stack.top[-1 - distance];
|
auto a = stack.pop().as!pre;
|
||||||
|
stack.push(Value.from(mixin("a", op, "b")));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
while(true){
|
while(true){
|
||||||
debug(traceExec){
|
debug(traceExec){
|
||||||
stderr.writeln(" ", stack.live);
|
printf(" ");
|
||||||
disassembleInstruction(chunk, ip - &chunk.code[0]);
|
foreach(slot; stack.live){
|
||||||
|
printf("[ ");
|
||||||
|
slot.print();
|
||||||
|
printf(" ]");
|
||||||
}
|
}
|
||||||
OpCode instruction = readIns();
|
printf("\n");
|
||||||
with(OpCode) opSwitch: final switch(instruction){
|
disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
|
||||||
case Constant:
|
}
|
||||||
|
OpCode instruction;
|
||||||
|
opSwitch: final switch(instruction = cast(OpCode)readByte()){
|
||||||
|
case OpCode.Constant:
|
||||||
Value constant = readConstant();
|
Value constant = readConstant();
|
||||||
stack.push(constant);
|
stack.push(constant);
|
||||||
break;
|
break;
|
||||||
case True:
|
case OpCode.Nil: stack.push(Value.nil); break;
|
||||||
stack.push(Value.bln(true));
|
case OpCode.True: stack.push(Value.from(true)); break;
|
||||||
break;
|
case OpCode.False: stack.push(Value.from(false)); break;
|
||||||
case False:
|
|
||||||
stack.push(Value.bln(false));
|
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
|
||||||
break;
|
|
||||||
case Nil:
|
|
||||||
stack.push(Value.nil());
|
|
||||||
break;
|
|
||||||
static foreach(k, op; [ Add: "+", Subtract: "-", Multiply: "*", Divide: "/" ]){
|
|
||||||
case k:
|
case k:
|
||||||
static if(k == Add){
|
binaryOp!(op, true, null, Value.Type.None);
|
||||||
if(peek(0).isStr && peek(1).isStr){
|
|
||||||
const(Obj.String)* b = stack.pop().getStr;
|
|
||||||
const(Obj.String)* a = stack.pop().getStr;
|
|
||||||
Obj.String* newStr = Obj.String.concat(a, b, &this);
|
|
||||||
stack.push(Value.str(newStr));
|
|
||||||
break opSwitch;
|
break opSwitch;
|
||||||
}
|
}
|
||||||
}
|
static foreach(k, op; [ OpCode.Greater: ">", OpCode.Less: "<", OpCode.GreaterEqual: ">=", OpCode.LessEqual: "<=" ]){
|
||||||
if(!peek(0).isNum || !peek(1).isNum){
|
case k:
|
||||||
runtimeError("Operands must be numbers.");
|
if(binaryOp!(op, checkSameType, "Operands must be of the same type.", Value.Type.None))
|
||||||
return InterpretResult.RuntimeError;
|
return InterpretResult.RuntimeError;
|
||||||
}
|
|
||||||
double b = stack.pop().getNum;
|
|
||||||
double a = stack.pop().getNum;
|
|
||||||
stack.push(Value.num(mixin("a", op, "b")));
|
|
||||||
break opSwitch;
|
break opSwitch;
|
||||||
}
|
}
|
||||||
static foreach(k, op; [ NotEqual: "!=", Equal: "==", Greater: ">", GreaterEqual: ">=", Less: "<", LessEqual: "<=" ]){
|
|
||||||
|
static foreach(k, op; [ OpCode.Add: "+", OpCode.Subtract: "-", OpCode.Multiply: "*", OpCode.Divide: "-" ]){
|
||||||
case k:
|
case k:
|
||||||
Value b = stack.pop();
|
static if(k == OpCode.Add){
|
||||||
Value a = stack.pop();
|
if(checkBinaryType!(Obj.Type.String)){
|
||||||
stack.push(Value.bln(compare!op(a, b)));
|
Obj.String* b = stack.pop().asObj.asString;
|
||||||
|
Obj.String* a = stack.pop().asObj.asString;
|
||||||
|
Obj.String* result = Obj.String.concat(a, b);
|
||||||
|
stack.push(Value.from(result));
|
||||||
break opSwitch;
|
break opSwitch;
|
||||||
}
|
}
|
||||||
case Not:
|
}
|
||||||
stack.push(Value.bln(stack.pop().isFalsey));
|
if(binaryOp!(op, checkBinaryType!(Value.Type.Number), "Operands must be numbers.", Value.Type.Number)())
|
||||||
|
return InterpretResult.RuntimeError;
|
||||||
|
break opSwitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode.Not:
|
||||||
|
stack.push(Value.from(stack.pop().isFalsey));
|
||||||
break;
|
break;
|
||||||
case Negate:
|
case OpCode.Negate:
|
||||||
if(!peek(0).isNum){
|
if(!peek(0).isType(Value.Type.Number)){
|
||||||
runtimeError("Operand must be a number.");
|
runtimeError("Operand must be a number.");
|
||||||
return InterpretResult.RuntimeError;
|
return InterpretResult.RuntimeError;
|
||||||
}
|
}
|
||||||
stack.push(Value.num(-stack.pop().getNum));
|
stack.push(Value.from(-stack.pop().asNumber));
|
||||||
break;
|
break;
|
||||||
case Return:
|
case OpCode.Return:
|
||||||
debug printValue(stack.pop());
|
stack.pop().print();
|
||||||
debug stderr.writeln();
|
printf("\n");
|
||||||
return InterpretResult.Ok;
|
return InterpretResult.Ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
void freeObjects(){
|
|
||||||
for(Obj* obj = objects; obj !is null;){
|
|
||||||
Obj* next = obj.next;
|
|
||||||
obj.freeObject();
|
|
||||||
obj = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue