Calls and Functions 24
This commit is contained in:
parent
2cd7a44bc7
commit
a45f6a9e17
18 changed files with 550 additions and 285 deletions
19
dub.sdl
19
dub.sdl
|
|
@ -3,27 +3,30 @@ description "A minimal D application."
|
||||||
authors "tanya"
|
authors "tanya"
|
||||||
copyright "Copyright © 2025, tanya"
|
copyright "Copyright © 2025, tanya"
|
||||||
license "MPL-2.0"
|
license "MPL-2.0"
|
||||||
dependency "commandr" version="~>1.1.0"
|
|
||||||
dependency "colored" version="~>0.0.33"
|
|
||||||
targetType "executable"
|
targetType "executable"
|
||||||
sourcePaths
|
sourcePaths
|
||||||
|
|
||||||
configuration "clox" {
|
configuration "clox" {
|
||||||
buildOptions "betterC"
|
buildOptions "betterC"
|
||||||
/* debugVersions "memTrace" */
|
sourcePaths "src/clox"
|
||||||
/* debugVersions "traceExec" */
|
buildRequirements "requireContracts"
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration "clox-dbg" {
|
||||||
|
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||||
|
debugVersions "memTrace"
|
||||||
debugVersions "printCode"
|
debugVersions "printCode"
|
||||||
|
debugVersions "traceExec"
|
||||||
|
dependency "colored" version="~>0.0.33"
|
||||||
dflags "-checkaction=C"
|
dflags "-checkaction=C"
|
||||||
libs "libbacktrace"
|
libs "libbacktrace"
|
||||||
targetType "executable"
|
|
||||||
sourcePaths "src/clox"
|
sourcePaths "src/clox"
|
||||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration "jlox" {
|
configuration "jlox" {
|
||||||
targetType "executable"
|
dependency "colored" version="~>0.0.33"
|
||||||
|
dependency "commandr" version="~>1.1.0"
|
||||||
sourcePaths "src/jlox" "src/common"
|
sourcePaths "src/jlox" "src/common"
|
||||||
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
|
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
|
||||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ struct OpColour{
|
||||||
enum OpConst;
|
enum OpConst;
|
||||||
enum OpStack;
|
enum OpStack;
|
||||||
enum OpJump;
|
enum OpJump;
|
||||||
|
enum OpCall;
|
||||||
|
|
||||||
enum OpCode : ubyte{
|
enum OpCode : ubyte{
|
||||||
@OpConst @(OpColour("200", "200", "100")) Constant,
|
@OpConst @(OpColour("200", "200", "100")) Constant,
|
||||||
|
|
@ -48,7 +49,9 @@ enum OpCode : ubyte{
|
||||||
@(OpColour("200", "100", "100")) Not,
|
@(OpColour("200", "100", "100")) Not,
|
||||||
@(OpColour("100", "100", "200")) Negate,
|
@(OpColour("100", "100", "200")) Negate,
|
||||||
@(OpColour("000", "200", "100")) Print,
|
@(OpColour("000", "200", "100")) Print,
|
||||||
@(OpColour("000", "200", "100")) Return,
|
|
||||||
|
@OpCall @(OpColour("250", "200", "250")) Call,
|
||||||
|
@(OpColour("250", "190", "200")) Return,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Chunk{
|
struct Chunk{
|
||||||
|
|
@ -68,12 +71,12 @@ struct Chunk{
|
||||||
code ~= b;
|
code ~= b;
|
||||||
lines ~= line;
|
lines ~= line;
|
||||||
}
|
}
|
||||||
size_t addConstant(Value value){
|
int addConstant(Value value){
|
||||||
long index = constants[].countUntil(value);
|
int index = cast(int)constants[].countUntil(value);
|
||||||
if(index >= 0)
|
if(index >= 0)
|
||||||
return index;
|
return index;
|
||||||
constants ~= value;
|
constants ~= value;
|
||||||
return constants.count - 1;
|
return cast(int)constants.count - 1;
|
||||||
}
|
}
|
||||||
void free(){
|
void free(){
|
||||||
code.free();
|
code.free();
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,21 @@ import core.stdc.stdio;
|
||||||
import clox.scanner;
|
import clox.scanner;
|
||||||
import clox.parser;
|
import clox.parser;
|
||||||
import clox.chunk;
|
import clox.chunk;
|
||||||
import clox.emitter;
|
|
||||||
import clox.dbg;
|
import clox.dbg;
|
||||||
|
import clox.vm;
|
||||||
|
import clox.object;
|
||||||
|
import clox.value;
|
||||||
import clox.container.dynarray;
|
import clox.container.dynarray;
|
||||||
|
import clox.container.varint;
|
||||||
|
|
||||||
|
Parser parser;
|
||||||
|
Compiler* currentCompiler;
|
||||||
|
|
||||||
struct Compiler{
|
struct Compiler{
|
||||||
Scanner scanner;
|
Compiler* enclosing;
|
||||||
Parser parser;
|
Obj.Function* func;
|
||||||
Emitter emitter;
|
enum FunctionType{ None, Function, Script }
|
||||||
|
FunctionType ftype = FunctionType.Script;
|
||||||
struct Local{
|
struct Local{
|
||||||
enum Uninitialised = -1;
|
enum Uninitialised = -1;
|
||||||
Token name;
|
Token name;
|
||||||
|
|
@ -21,28 +28,58 @@ struct Compiler{
|
||||||
DynArray!Local locals;
|
DynArray!Local locals;
|
||||||
int scopeDepth;
|
int scopeDepth;
|
||||||
private Chunk* compilingChunk;
|
private Chunk* compilingChunk;
|
||||||
Chunk* currentChunk() => compilingChunk;
|
Chunk* currentChunk() => &func.chunk;
|
||||||
bool compile(const(char)* source, Chunk* chunk){
|
void initialise(FunctionType ftype){
|
||||||
scanner.initialise(source);
|
this.ftype = ftype;
|
||||||
parser.initialise(&this);
|
enclosing = currentCompiler;
|
||||||
emitter.initialise(&this);
|
currentCompiler = &this;
|
||||||
locals.initialise();
|
locals.initialise();
|
||||||
compilingChunk = chunk;
|
|
||||||
|
|
||||||
|
func = Obj.Function.create();
|
||||||
|
if(ftype == FunctionType.Function){
|
||||||
|
currentCompiler.func.name = Obj.String.copy(parser.previous.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local local = Local();
|
||||||
|
local.depth = 0;
|
||||||
|
local.name.lexeme = "";
|
||||||
|
locals ~= local;
|
||||||
|
}
|
||||||
|
Obj.Function* compile(){
|
||||||
parser.advance();
|
parser.advance();
|
||||||
while(!parser.match(Token.Type.EOF)){
|
while(!parser.match(Token.Type.EOF)){
|
||||||
parser.declaration();
|
parser.declaration();
|
||||||
}
|
}
|
||||||
end();
|
|
||||||
return !parser.hadError;
|
Obj.Function* f = end();
|
||||||
|
return parser.hadError ? null : f;
|
||||||
}
|
}
|
||||||
void end(){
|
Obj.Function* end(){
|
||||||
emitter.emitReturn();
|
emitReturn();
|
||||||
debug(printCode){
|
Obj.Function* f = func;
|
||||||
|
debug(printCode) if(vm.printCode){
|
||||||
if(!parser.hadError)
|
if(!parser.hadError)
|
||||||
currentChunk.disassembleChunk();
|
currentChunk.disassembleChunk(func.name !is null ? func.name.chars.ptr : "<script>");
|
||||||
}
|
}
|
||||||
locals.free();
|
locals.free();
|
||||||
|
currentCompiler = enclosing;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void emit(Args...)(Args a){
|
||||||
|
static foreach(b; a)
|
||||||
|
currentCompiler.currentChunk.write(b, parser.previous.line);
|
||||||
|
}
|
||||||
|
void emitReturn(){
|
||||||
|
emit(OpCode.Nil, OpCode.Return);
|
||||||
|
}
|
||||||
|
void emitConstant(Value value){
|
||||||
|
int c = makeConstant(value);
|
||||||
|
emit(OpCode.Constant, VarUint(c).bytes);
|
||||||
|
}
|
||||||
|
int makeConstant(Value value){
|
||||||
|
int constant = currentCompiler.currentChunk.addConstant(value);
|
||||||
|
return constant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,31 @@ module clox.container.dynarray;
|
||||||
import clox.memory;
|
import clox.memory;
|
||||||
|
|
||||||
struct DynArray(T){
|
struct DynArray(T){
|
||||||
size_t count;
|
private size_t _count;
|
||||||
size_t capacity;
|
private size_t _capacity;
|
||||||
|
ref size_t count() => _count;
|
||||||
|
size_t capacity() => _capacity;
|
||||||
|
// TODO capacity setter
|
||||||
T* ptr;
|
T* ptr;
|
||||||
void initialise(){
|
void initialise(){
|
||||||
count = 0;
|
_count = 0;
|
||||||
capacity = 0;
|
_capacity = 0;
|
||||||
ptr = null;
|
ptr = null;
|
||||||
}
|
}
|
||||||
void opOpAssign(string op: "~")(in T value){
|
void opOpAssign(string op: "~")(in T value){
|
||||||
if(capacity < count + 1){
|
if(capacity < count + 1){
|
||||||
size_t oldCapacity = capacity;
|
size_t oldCapacity = capacity;
|
||||||
capacity = GROW_CAPACITY(oldCapacity);
|
_capacity = GROW_CAPACITY(oldCapacity);
|
||||||
ptr = GROW_ARRAY!T(ptr, oldCapacity, capacity);
|
ptr = GROW_ARRAY!T(ptr, oldCapacity, capacity);
|
||||||
}
|
}
|
||||||
ptr[count] = value;
|
ptr[count] = value;
|
||||||
count++;
|
_count++;
|
||||||
}
|
}
|
||||||
auto opSlice(){
|
ref auto opSlice(){
|
||||||
assert(ptr || !count);
|
assert(ptr || !count);
|
||||||
return ptr[0 .. count];
|
return ptr[0 .. count];
|
||||||
}
|
}
|
||||||
auto opSlice(size_t i, size_t o){
|
ref auto opSlice(size_t i, size_t o){
|
||||||
assert(ptr || !count);
|
assert(ptr || !count);
|
||||||
return ptr[i .. o];
|
return ptr[i .. o];
|
||||||
}
|
}
|
||||||
|
|
@ -35,15 +38,19 @@ struct DynArray(T){
|
||||||
size_t opDollar(size_t pos: 0)(){
|
size_t opDollar(size_t pos: 0)(){
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
auto pop(){
|
||||||
|
assert(count);
|
||||||
|
return this[--_count];
|
||||||
|
}
|
||||||
|
void reset(){
|
||||||
|
_count = 0;
|
||||||
|
debug foreach(ref item; this[])
|
||||||
|
item = T.init;
|
||||||
|
}
|
||||||
void free(){
|
void free(){
|
||||||
if(ptr)
|
if(ptr)
|
||||||
FREE_ARRAY!T(ptr, capacity);
|
FREE_ARRAY!T(ptr, capacity);
|
||||||
initialise();
|
initialise();
|
||||||
}
|
}
|
||||||
auto pop(){
|
|
||||||
assert(count);
|
|
||||||
count--;
|
|
||||||
return this[count-1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
module clox.container.stack;
|
|
||||||
|
|
||||||
import clox.container.dynarray;
|
|
||||||
|
|
||||||
struct Stack(T){
|
|
||||||
T* top;
|
|
||||||
private DynArray!T data;
|
|
||||||
void initialise(){
|
|
||||||
data.initialise();
|
|
||||||
top = data.ptr;
|
|
||||||
}
|
|
||||||
void opOpAssign(string op: "~")(T value){
|
|
||||||
data ~= value;
|
|
||||||
top = data.ptr + data.count;
|
|
||||||
}
|
|
||||||
T pop(){
|
|
||||||
assert(top > data.ptr);
|
|
||||||
T t = *(--top);
|
|
||||||
assert(data.count-- >= 0);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
ref T opIndex(size_t i) => data[i];
|
|
||||||
const(T)[] live(){
|
|
||||||
return data[];
|
|
||||||
}
|
|
||||||
void free(){
|
|
||||||
data.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -28,8 +28,10 @@ private long constantInstruction(alias OpCode op)(const char* name, Chunk* chunk
|
||||||
private long jumpInstruction(alias OpCode op)(const char* name, int sign, Chunk* chunk, long offset){
|
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];
|
ushort jump = cast(ushort)(chunk.code[offset + 1] << 8) | chunk.code[offset + 2];
|
||||||
enum c = getUDAs!(op, OpColour)[0];
|
enum c = getUDAs!(op, OpColour)[0];
|
||||||
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4ld ", Colour.Green)).ptr, name, (jump * sign) + offset + 3);
|
long pl = printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4ld ", Colour.Green)).ptr, name, (jump * sign) + offset + 3);
|
||||||
printf("%i ", sign * jump);
|
pl += printf("%i ", sign * jump);
|
||||||
|
foreach(i; 0 .. 70-pl)
|
||||||
|
printf(" ");
|
||||||
return offset + 3;
|
return offset + 3;
|
||||||
}
|
}
|
||||||
private long stackIndexInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
|
private long stackIndexInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
|
||||||
|
|
@ -39,6 +41,13 @@ private long stackIndexInstruction(alias OpCode op)(const char* name, Chunk* chu
|
||||||
printf(" ");
|
printf(" ");
|
||||||
return offset + 1 + index.len;
|
return offset + 1 + index.len;
|
||||||
}
|
}
|
||||||
|
private long callInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
|
||||||
|
VarUint index = VarUint.read(&chunk.code[offset + 1]);
|
||||||
|
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(" ");
|
||||||
|
return offset + 1 + index.len;
|
||||||
|
}
|
||||||
|
|
||||||
void printHeader(Chunk* chunk, const char* name = "execution"){
|
void printHeader(Chunk* chunk, const char* name = "execution"){
|
||||||
printf(" ============== %s ==============\n", name);
|
printf(" ============== %s ==============\n", name);
|
||||||
|
|
@ -74,6 +83,8 @@ long disassembleInstruction(Chunk* chunk, long offset){
|
||||||
case e: return stackIndexInstruction!e(e.stringof, chunk, offset);
|
case e: return stackIndexInstruction!e(e.stringof, chunk, offset);
|
||||||
} else static if(hasUDA!(e, OpJump)){
|
} else static if(hasUDA!(e, OpJump)){
|
||||||
case e: return jumpInstruction!e(e.stringof, e == OpCode.Loop ? -1 : 1, chunk, 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 {
|
} else {
|
||||||
case e: return simpleInstruction!e(e.stringof, offset);
|
case e: return simpleInstruction!e(e.stringof, offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
module clox.emitter;
|
|
||||||
|
|
||||||
import core.stdc.stdio;
|
|
||||||
|
|
||||||
import clox.chunk;
|
|
||||||
import clox.compiler;
|
|
||||||
import clox.value;
|
|
||||||
import clox.container.varint;
|
|
||||||
|
|
||||||
struct Emitter{
|
|
||||||
Compiler* compiler;
|
|
||||||
void initialise(Compiler* compiler){
|
|
||||||
this.compiler = compiler;
|
|
||||||
}
|
|
||||||
void emit(Args...)(Args a){
|
|
||||||
static foreach(b; a)
|
|
||||||
compiler.currentChunk.write(b, compiler.parser.previous.line);
|
|
||||||
}
|
|
||||||
void emitReturn(){
|
|
||||||
emit(OpCode.Return);
|
|
||||||
}
|
|
||||||
void emitConstant(Value value){
|
|
||||||
size_t c = makeConstant(value);
|
|
||||||
emit(OpCode.Constant, VarUint(c).bytes);
|
|
||||||
}
|
|
||||||
size_t makeConstant(Value value){
|
|
||||||
size_t constant = compiler.currentChunk.addConstant(value);
|
|
||||||
return constant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
module clox.main;
|
module clox.main;
|
||||||
|
|
||||||
|
import std.algorithm;
|
||||||
import core.sys.posix.unistd : STDIN_FILENO;
|
import core.sys.posix.unistd : STDIN_FILENO;
|
||||||
import core.stdc.stdio;
|
import core.stdc.stdio;
|
||||||
import core.stdc.stdlib;
|
import core.stdc.stdlib;
|
||||||
|
|
@ -35,28 +36,52 @@ int runStdin(){
|
||||||
source.free();
|
source.free();
|
||||||
return vm.interpret(source).interpretResult;
|
return vm.interpret(source).interpretResult;
|
||||||
}
|
}
|
||||||
int runFile(const char* path){
|
int runFile(const char[] path){
|
||||||
char* source = path.readFile();
|
auto source = path.readFile();
|
||||||
if(!source)
|
if(!source)
|
||||||
return 74;
|
return 74;
|
||||||
scope(exit)
|
scope(exit)
|
||||||
source.free();
|
source.ptr.free();
|
||||||
return vm.interpret(source).interpretResult;
|
return vm.interpret(source.ptr).interpretResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
int runMain(ulong argc, const char** argv){
|
int runMain(ulong argc, const char** argv){
|
||||||
|
auto usage(){
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
const(char)[] file;
|
||||||
|
|
||||||
|
foreach(arg; argv[1 .. argc].map!parse){
|
||||||
|
if(arg[0] == '-'){
|
||||||
|
foreach(c; arg[1 .. $]){
|
||||||
|
switch(c){
|
||||||
|
debug(printCode) case 'p': vm.printCode = true; break;
|
||||||
|
debug(traceExec) case 't': vm.traceExec = true; break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vm.initialise();
|
vm.initialise();
|
||||||
scope(exit)
|
scope(exit)
|
||||||
vm.free();
|
vm.free();
|
||||||
|
|
||||||
if(argc == 1 && isatty(STDIN_FILENO)){
|
if(!file && isatty(STDIN_FILENO)){
|
||||||
return runPrompt();
|
return runPrompt();
|
||||||
} else if(argc == 1){
|
} else if(!file){
|
||||||
return runStdin();
|
return runStdin();
|
||||||
} else if(argc == 2){
|
} else if(file){
|
||||||
return runFile(argv[1]);
|
return runFile(file);
|
||||||
} else {
|
} else {
|
||||||
stderr.fprintf("Usage: lox [path]\n");
|
usage();
|
||||||
return 64;
|
return 64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ module clox.memory;
|
||||||
|
|
||||||
import core.stdc.stdlib;
|
import core.stdc.stdlib;
|
||||||
|
|
||||||
|
import clox.util;
|
||||||
|
|
||||||
auto GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
|
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 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_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
|
||||||
|
|
@ -29,9 +31,14 @@ version(D_BetterC) {} else {
|
||||||
import std.demangle;
|
import std.demangle;
|
||||||
BackTraceBuilder* btb = cast(BackTraceBuilder*)data;
|
BackTraceBuilder* btb = cast(BackTraceBuilder*)data;
|
||||||
if(func){
|
if(func){
|
||||||
string funcStr = func.to!string.demangle;
|
import std.string;
|
||||||
btb.funcStack ~= funcStr;
|
try{
|
||||||
|
btb.fileStack ~= filename.to!string;
|
||||||
|
btb.funcStack ~= func.to!string.demangle.stripLeft("nothrow ").stripLeft("@nogc ");
|
||||||
btb.lineStack ~= lineno;
|
btb.lineStack ~= lineno;
|
||||||
|
} catch(Exception){
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -41,10 +48,14 @@ version(D_BetterC) {} else {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BackTraceBuilder{
|
struct BackTraceBuilder{
|
||||||
|
string[] fileStack;
|
||||||
string[] funcStack;
|
string[] funcStack;
|
||||||
int[] lineStack;
|
int[] lineStack;
|
||||||
string toString() const{
|
string toString() const{
|
||||||
return zip(funcStack, lineStack).map!"'\t' ~ a[0] ~ ` : ` ~ a[1].to!string".join("\n");
|
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 createBacktrace(int skip = 1) nothrow{
|
||||||
|
|
@ -62,7 +73,7 @@ version(D_BetterC) {} else {
|
||||||
ulong totalAllocs, totalReallocs, totalFrees;
|
ulong totalAllocs, totalReallocs, totalFrees;
|
||||||
ulong totalAllocBytes, totalFreedBytes;
|
ulong totalAllocBytes, totalFreedBytes;
|
||||||
static ~this(){
|
static ~this(){
|
||||||
stderr.writefln("Allocs: %d (%d bytes), Reallocs: %d, Frees: %d (%d bytes)", totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes);
|
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes)", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes);
|
||||||
if(totalAllocBytes > totalFreedBytes){
|
if(totalAllocBytes > totalFreedBytes){
|
||||||
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
|
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
|
||||||
|
|
||||||
|
|
@ -123,8 +134,17 @@ void freeObject(Obj* object){
|
||||||
final switch(object.type){
|
final switch(object.type){
|
||||||
case Obj.Type.String:
|
case Obj.Type.String:
|
||||||
Obj.String* str = cast(Obj.String*)object;
|
Obj.String* str = cast(Obj.String*)object;
|
||||||
FREE_ARRAY!char(str.chars.ptr, str.chars.length + 1);
|
FREE_ARRAY(str.chars.ptr, str.chars.length + 1);
|
||||||
FREE!(Obj.String)(cast(Obj.String*)object);
|
FREE(str);
|
||||||
|
break;
|
||||||
|
case Obj.Type.Function:
|
||||||
|
Obj.Function* func = cast(Obj.Function*)object;
|
||||||
|
func.chunk.free();
|
||||||
|
FREE(func);
|
||||||
|
break;
|
||||||
|
case Obj.Type.NativeFunction:
|
||||||
|
Obj.NativeFunction* func = cast(Obj.NativeFunction*)object;
|
||||||
|
FREE(func);
|
||||||
break;
|
break;
|
||||||
case Obj.Type.None: assert(0);
|
case Obj.Type.None: assert(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ module clox.object;
|
||||||
|
|
||||||
import core.stdc.stdio;
|
import core.stdc.stdio;
|
||||||
|
|
||||||
|
import clox.chunk;
|
||||||
|
import clox.container.table;
|
||||||
import clox.memory;
|
import clox.memory;
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.vm;
|
import clox.vm;
|
||||||
import clox.container.table;
|
|
||||||
|
|
||||||
struct Obj{
|
struct Obj{
|
||||||
enum Type{
|
enum Type{
|
||||||
None, String,
|
None, String, Function, NativeFunction
|
||||||
}
|
}
|
||||||
Type type;
|
Type type;
|
||||||
Obj* next;
|
Obj* next;
|
||||||
|
|
@ -19,17 +20,27 @@ struct Obj{
|
||||||
static if(type != Type.None)
|
static if(type != Type.None)
|
||||||
assert(this.type == type);
|
assert(this.type == type);
|
||||||
static if(type == Type.String) return cast(String*)&this;
|
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.None) return &this;
|
static if(type == Type.None) return &this;
|
||||||
}
|
}
|
||||||
|
|
||||||
int print(const char* strFmt = `"%s"`) const{
|
int print(const char* strFmt = `"%s"`) const{
|
||||||
final switch(type){
|
final switch(type){
|
||||||
case Type.String: return printf(strFmt, asString.chars.ptr); break;
|
case Type.String: return printf(strFmt, asString.chars.ptr);
|
||||||
|
case Type.Function:
|
||||||
|
Function* func = asFunc;
|
||||||
|
if(func.name !is null)
|
||||||
|
return printf("<fn %s/%d>", func.name.chars.ptr, func.arity);
|
||||||
|
return printf("<script>");
|
||||||
|
case Type.NativeFunction: return printf("<native fn>");
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String* asString() const pure => as!(Type.String);
|
String* asString() const pure => as!(Type.String);
|
||||||
|
Function* asFunc() const pure => as!(Type.Function);
|
||||||
|
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
|
||||||
|
|
||||||
private static T* allocateObject(T)(){
|
private static T* allocateObject(T)(){
|
||||||
Obj* object = reallocate!Obj(null, 0, T.sizeof);
|
Obj* object = reallocate!Obj(null, 0, T.sizeof);
|
||||||
|
|
@ -81,17 +92,49 @@ struct Obj{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Function{
|
||||||
|
static enum myType = Type.Function;
|
||||||
|
Obj obj;
|
||||||
|
int arity;
|
||||||
|
Chunk chunk;
|
||||||
|
String* name;
|
||||||
|
static Function* create(){
|
||||||
|
Function* func = allocateObject!(typeof(this))();
|
||||||
|
func.arity = 0;
|
||||||
|
func.chunk = Chunk.init;
|
||||||
|
func.chunk.initialise();
|
||||||
|
func.name = null;
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NativeFunction{
|
||||||
|
static enum myType = Type.NativeFunction;
|
||||||
|
alias Sig = Value function(int argCount, Value* args);
|
||||||
|
Obj obj;
|
||||||
|
Sig func;
|
||||||
|
static NativeFunction* create(Sig func){
|
||||||
|
Obj.NativeFunction* native = allocateObject!(typeof(this))();
|
||||||
|
native.func = func;
|
||||||
|
return native;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool opEquals(in ref Obj rhs) const pure{
|
bool opEquals(in ref Obj rhs) const pure{
|
||||||
if(rhs.type != type)
|
if(rhs.type != type)
|
||||||
return false;
|
return false;
|
||||||
final switch(type){
|
final switch(type){
|
||||||
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
case Type.String: return asString.chars.ptr is rhs.asString.chars.ptr;
|
||||||
|
case Type.Function: return &this == &rhs;
|
||||||
|
case Type.NativeFunction: return &this == &rhs;
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool opCmp(in ref Obj rhs) const pure{
|
bool opCmp(in ref Obj rhs) const pure{
|
||||||
final switch(type){
|
final switch(type){
|
||||||
case Type.String: return asString.chars > rhs.asString.chars;
|
case Type.String: return asString.chars > rhs.asString.chars;
|
||||||
|
case Type.Function: return false;
|
||||||
|
case Type.NativeFunction: return false;
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +143,8 @@ struct Obj{
|
||||||
static string toString(Obj* obj){
|
static string toString(Obj* obj){
|
||||||
final switch(obj.type){
|
final switch(obj.type){
|
||||||
case Type.String: return '"' ~ obj.asString.chars.idup ~ '"';
|
case Type.String: return '"' ~ obj.asString.chars.idup ~ '"';
|
||||||
|
case Type.Function: return "Function";
|
||||||
|
case Type.NativeFunction: return "NativeFunction";
|
||||||
case Type.None: assert(0);
|
case Type.None: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,12 @@ import clox.vm;
|
||||||
import clox.container.varint;
|
import clox.container.varint;
|
||||||
|
|
||||||
struct Parser{
|
struct Parser{
|
||||||
Compiler* compiler;
|
|
||||||
Token current, previous;
|
Token current, previous;
|
||||||
bool hadError, panicMode;
|
bool hadError, panicMode;
|
||||||
void initialise(Compiler* compiler){
|
|
||||||
this.compiler = compiler;
|
|
||||||
}
|
|
||||||
void advance(){
|
void advance(){
|
||||||
previous = current;
|
previous = current;
|
||||||
while(true){
|
while(true){
|
||||||
current = compiler.scanner.scanToken();
|
current = scanner.scanToken();
|
||||||
if(current.type != Token.Type.Error)
|
if(current.type != Token.Type.Error)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -52,28 +48,36 @@ struct Parser{
|
||||||
expression();
|
expression();
|
||||||
if(vm.isREPL){
|
if(vm.isREPL){
|
||||||
match(Token.Type.Semicolon);
|
match(Token.Type.Semicolon);
|
||||||
compiler.emitter.emit(OpCode.Print);
|
currentCompiler.emit(OpCode.Print);
|
||||||
} else {
|
} else {
|
||||||
consume(Token.Type.Semicolon, "Expect ';' after expression.");
|
consume(Token.Type.Semicolon, "Expect ';' after expression.");
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void varDeclaration(){
|
void varDeclaration(){
|
||||||
long global = parseVariable("Expect variable name.");
|
int global = parseVariable("Expect variable name.");
|
||||||
if(match(Token.Type.Equal)){
|
if(match(Token.Type.Equal)){
|
||||||
expression();
|
expression();
|
||||||
} else {
|
} else {
|
||||||
compiler.emitter.emit(OpCode.Nil);
|
currentCompiler.emit(OpCode.Nil);
|
||||||
}
|
}
|
||||||
consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
|
consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
|
||||||
defineVariable(global);
|
defineVariable(global);
|
||||||
}
|
}
|
||||||
|
void funDeclaration(){
|
||||||
|
int global = parseVariable("Expect function name");
|
||||||
|
markInitialised();
|
||||||
|
func();
|
||||||
|
defineVariable(global);
|
||||||
|
}
|
||||||
void declaration(){
|
void declaration(){
|
||||||
if(match(Token.Type.Var))
|
if(match(Token.Type.Var))
|
||||||
varDeclaration();
|
varDeclaration();
|
||||||
|
else if(match(Token.Type.Fun))
|
||||||
|
funDeclaration();
|
||||||
else
|
else
|
||||||
statement();
|
statement();
|
||||||
if(compiler.parser.panicMode)
|
if(parser.panicMode)
|
||||||
synchronise();
|
synchronise();
|
||||||
}
|
}
|
||||||
void statement(){
|
void statement(){
|
||||||
|
|
@ -81,6 +85,8 @@ struct Parser{
|
||||||
printStatement();
|
printStatement();
|
||||||
} else if(match(Token.Type.If)){
|
} else if(match(Token.Type.If)){
|
||||||
ifStatement();
|
ifStatement();
|
||||||
|
} else if(match(Token.Type.Return)){
|
||||||
|
returnStatement();
|
||||||
} else if(match(Token.Type.While)){
|
} else if(match(Token.Type.While)){
|
||||||
whileStatement();
|
whileStatement();
|
||||||
} else if(match(Token.Type.For)){
|
} else if(match(Token.Type.For)){
|
||||||
|
|
@ -95,7 +101,19 @@ struct Parser{
|
||||||
void printStatement(){
|
void printStatement(){
|
||||||
expression();
|
expression();
|
||||||
consume(Token.Type.Semicolon, "Expect ';' after value.");
|
consume(Token.Type.Semicolon, "Expect ';' after value.");
|
||||||
compiler.emitter.emit(OpCode.Print);
|
currentCompiler.emit(OpCode.Print);
|
||||||
|
}
|
||||||
|
void returnStatement(){
|
||||||
|
if(currentCompiler.ftype == Compiler.FunctionType.Script){
|
||||||
|
error("Can't return from top-level code.");
|
||||||
|
}
|
||||||
|
if(match(Token.Type.Semicolon)){
|
||||||
|
currentCompiler.emitReturn();
|
||||||
|
} else {
|
||||||
|
expression();
|
||||||
|
consume(Token.Type.Semicolon, "Expect ';' after return value.");
|
||||||
|
currentCompiler.emit(OpCode.Return);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void ifStatement(){
|
void ifStatement(){
|
||||||
consume(Token.Type.LeftParen, "Expect '(' after 'if'.");
|
consume(Token.Type.LeftParen, "Expect '(' after 'if'.");
|
||||||
|
|
@ -103,13 +121,13 @@ struct Parser{
|
||||||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||||
|
|
||||||
int thenJump = emitJump(OpCode.JumpIfFalse);
|
int thenJump = emitJump(OpCode.JumpIfFalse);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
statement();
|
statement();
|
||||||
|
|
||||||
int elseJump = emitJump(OpCode.Jump);
|
int elseJump = emitJump(OpCode.Jump);
|
||||||
|
|
||||||
patchJump(thenJump);
|
patchJump(thenJump);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
|
|
||||||
if(match(Token.Type.Else))
|
if(match(Token.Type.Else))
|
||||||
statement();
|
statement();
|
||||||
|
|
@ -117,18 +135,18 @@ struct Parser{
|
||||||
patchJump(elseJump);
|
patchJump(elseJump);
|
||||||
}
|
}
|
||||||
void whileStatement(){
|
void whileStatement(){
|
||||||
int loopStart = cast(int)compiler.currentChunk.code.count;
|
int loopStart = cast(int)currentCompiler.currentChunk.code.count;
|
||||||
consume(Token.Type.LeftParen, "Expect '(' after 'while'.");
|
consume(Token.Type.LeftParen, "Expect '(' after 'while'.");
|
||||||
expression();
|
expression();
|
||||||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||||
|
|
||||||
int exitJump = emitJump(OpCode.JumpIfFalse);
|
int exitJump = emitJump(OpCode.JumpIfFalse);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
statement();
|
statement();
|
||||||
emitLoop(loopStart);
|
emitLoop(loopStart);
|
||||||
|
|
||||||
patchJump(exitJump);
|
patchJump(exitJump);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
}
|
}
|
||||||
void forStatement(){
|
void forStatement(){
|
||||||
beginScope();
|
beginScope();
|
||||||
|
|
@ -141,20 +159,20 @@ struct Parser{
|
||||||
expressionStatement();
|
expressionStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
int loopStart = cast(int)compiler.currentChunk.code.count;
|
int loopStart = cast(int)currentCompiler.currentChunk.code.count;
|
||||||
int exitJump = -1;
|
int exitJump = -1;
|
||||||
if(!match(Token.Type.Semicolon)){
|
if(!match(Token.Type.Semicolon)){
|
||||||
expression();
|
expression();
|
||||||
consume(Token.Type.Semicolon, "Expect ';' after loop condition.");
|
consume(Token.Type.Semicolon, "Expect ';' after loop condition.");
|
||||||
exitJump = emitJump(OpCode.JumpIfFalse);
|
exitJump = emitJump(OpCode.JumpIfFalse);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!match(Token.Type.RightParen)){
|
if(!match(Token.Type.RightParen)){
|
||||||
int bodyJump = emitJump(OpCode.Jump);
|
int bodyJump = emitJump(OpCode.Jump);
|
||||||
int incrementStart = cast(int)compiler.currentChunk.code.count;
|
int incrementStart = cast(int)currentCompiler.currentChunk.code.count;
|
||||||
expression();
|
expression();
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
consume(Token.Type.RightParen, "Expect ')' after for clauses.");
|
consume(Token.Type.RightParen, "Expect ')' after for clauses.");
|
||||||
emitLoop(loopStart);
|
emitLoop(loopStart);
|
||||||
loopStart = incrementStart;
|
loopStart = incrementStart;
|
||||||
|
|
@ -166,7 +184,7 @@ struct Parser{
|
||||||
|
|
||||||
if(exitJump != -1){
|
if(exitJump != -1){
|
||||||
patchJump(exitJump);
|
patchJump(exitJump);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
}
|
}
|
||||||
|
|
||||||
endScope();
|
endScope();
|
||||||
|
|
@ -176,65 +194,89 @@ struct Parser{
|
||||||
declaration();
|
declaration();
|
||||||
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
||||||
}
|
}
|
||||||
|
void func(){
|
||||||
|
Compiler compiler;
|
||||||
|
compiler.initialise(Compiler.FunctionType.Function);
|
||||||
|
beginScope();
|
||||||
|
|
||||||
|
consume(Token.Type.LeftParen, "Expect '(' after function name.");
|
||||||
|
if(!check(Token.Type.RightParen)){
|
||||||
|
do {
|
||||||
|
currentCompiler.func.arity++;
|
||||||
|
int constant = parseVariable("Expect parameter name.");
|
||||||
|
defineVariable(constant);
|
||||||
|
} while(match(Token.Type.Comma));
|
||||||
|
}
|
||||||
|
consume(Token.Type.RightParen, "Expect ')' after parameters.");
|
||||||
|
consume(Token.Type.LeftBrace, "Expect '{' before function body.");
|
||||||
|
block();
|
||||||
|
|
||||||
|
Obj.Function* func = compiler.end();
|
||||||
|
|
||||||
|
VarUint constant = currentCompiler.makeConstant(Value.from(func));
|
||||||
|
currentCompiler.emit(OpCode.Constant, constant.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
void patchJump(int offset){
|
void patchJump(int offset){
|
||||||
long jump = compiler.currentChunk.code.count - offset - 2;
|
int jump = cast(int)currentCompiler.currentChunk.code.count - offset - 2;
|
||||||
if(jump > ushort.max)
|
if(jump > ushort.max)
|
||||||
error("Too much code to jump over.");
|
error("Too much code to jump over.");
|
||||||
compiler.currentChunk.code[offset] = (jump >> 8) & 0xff;
|
currentCompiler.currentChunk.code[offset] = (jump >> 8) & 0xff;
|
||||||
compiler.currentChunk.code[offset + 1] = jump & 0xff;
|
currentCompiler.currentChunk.code[offset + 1] = jump & 0xff;
|
||||||
}
|
}
|
||||||
int emitJump(OpCode instruction){
|
int emitJump(OpCode instruction){
|
||||||
compiler.emitter.emit(instruction, ubyte(0xff), ubyte(0xff));
|
currentCompiler.emit(instruction, ubyte(0xff), ubyte(0xff));
|
||||||
return cast(int)compiler.currentChunk.code.count - 2;
|
return cast(int)currentCompiler.currentChunk.code.count - 2;
|
||||||
}
|
}
|
||||||
void emitLoop(int loopStart){
|
void emitLoop(int loopStart){
|
||||||
compiler.emitter.emit(OpCode.Loop);
|
currentCompiler.emit(OpCode.Loop);
|
||||||
|
|
||||||
int offset = cast(int)compiler.currentChunk.code.count - loopStart + 2;
|
int offset = cast(int)currentCompiler.currentChunk.code.count - loopStart + 2;
|
||||||
if(offset > ushort.max)
|
if(offset > ushort.max)
|
||||||
error("Loop body too large.");
|
error("Loop body too large.");
|
||||||
|
|
||||||
compiler.emitter.emit(cast(ubyte)((offset >> 8) & 0xff));
|
currentCompiler.emit(cast(ubyte)((offset >> 8) & 0xff));
|
||||||
compiler.emitter.emit(cast(ubyte)(offset & 0xff));
|
currentCompiler.emit(cast(ubyte)(offset & 0xff));
|
||||||
}
|
}
|
||||||
void defineVariable(long global){
|
void defineVariable(int global){
|
||||||
if(compiler.scopeDepth > 0){
|
if(currentCompiler.scopeDepth > 0){
|
||||||
markInitialised();
|
markInitialised();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
compiler.emitter.emit(OpCode.DefineGlobal, VarUint(global).bytes);
|
currentCompiler.emit(OpCode.DefineGlobal, VarUint(global).bytes);
|
||||||
}
|
}
|
||||||
void markInitialised(){
|
void markInitialised(){
|
||||||
compiler.locals[compiler.locals.count - 1].depth = compiler.scopeDepth;
|
if(currentCompiler.scopeDepth == 0)
|
||||||
|
return;
|
||||||
|
currentCompiler.locals[currentCompiler.locals.count - 1].depth = currentCompiler.scopeDepth;
|
||||||
}
|
}
|
||||||
void declareVariable(){
|
void declareVariable(){
|
||||||
if(compiler.scopeDepth == 0)
|
if(currentCompiler.scopeDepth == 0)
|
||||||
return;
|
return;
|
||||||
Token* name = &compiler.parser.previous;
|
Token* name = &parser.previous;
|
||||||
for(long i = compiler.locals.count - 1; i >= 0; i--){
|
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
|
||||||
Compiler.Local* local = &compiler.locals[i];
|
currentCompiler.Local* local = ¤tCompiler.locals[i];
|
||||||
if(local.depth != Compiler.Local.Uninitialised && local.depth < compiler.scopeDepth)
|
if(local.depth != currentCompiler.Local.Uninitialised && local.depth < currentCompiler.scopeDepth)
|
||||||
break;
|
break;
|
||||||
if(name.lexeme == local.name.lexeme)
|
if(name.lexeme == local.name.lexeme)
|
||||||
error("Already a variable with this name in this scope.");
|
error("Already a variable with this name in this scope.");
|
||||||
}
|
}
|
||||||
addLocal(*name);
|
addLocal(*name);
|
||||||
}
|
}
|
||||||
long parseVariable(const char* errorMessage){
|
int parseVariable(const char* errorMessage){
|
||||||
consume(Token.Type.Identifier, errorMessage);
|
consume(Token.Type.Identifier, errorMessage);
|
||||||
declareVariable();
|
declareVariable();
|
||||||
if(compiler.scopeDepth > 0)
|
if(currentCompiler.scopeDepth > 0)
|
||||||
return 0;
|
return 0;
|
||||||
return identifierConstant(&previous);
|
return identifierConstant(&previous);
|
||||||
}
|
}
|
||||||
long identifierConstant(Token* name){
|
int identifierConstant(Token* name){
|
||||||
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
|
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
|
||||||
return compiler.emitter.makeConstant(nameVal);
|
return currentCompiler.makeConstant(nameVal);
|
||||||
}
|
}
|
||||||
void namedVariable(Token name, bool canAssign){
|
void namedVariable(Token name, bool canAssign){
|
||||||
OpCode getOp, setOp;
|
OpCode getOp, setOp;
|
||||||
long arg = resolveLocal(&name);
|
int arg = resolveLocal(&name);
|
||||||
if(arg != -1){
|
if(arg != -1){
|
||||||
getOp = OpCode.GetLocal;
|
getOp = OpCode.GetLocal;
|
||||||
setOp = OpCode.SetLocal;
|
setOp = OpCode.SetLocal;
|
||||||
|
|
@ -245,17 +287,28 @@ struct Parser{
|
||||||
}
|
}
|
||||||
if(canAssign && match(Token.Type.Equal)){
|
if(canAssign && match(Token.Type.Equal)){
|
||||||
expression();
|
expression();
|
||||||
compiler.emitter.emit(setOp, VarUint(arg).bytes);
|
currentCompiler.emit(setOp, VarUint(arg).bytes);
|
||||||
} else {
|
} else {
|
||||||
compiler.emitter.emit(getOp, VarUint(arg).bytes);
|
currentCompiler.emit(getOp, VarUint(arg).bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int argumentList(){
|
||||||
|
int argCount = 0;
|
||||||
|
if(!check(Token.Type.RightParen)){
|
||||||
|
do {
|
||||||
|
expression();
|
||||||
|
argCount++;
|
||||||
|
} while(match(Token.Type.Comma));
|
||||||
|
}
|
||||||
|
consume(Token.Type.RightParen, "Expect ')' after arguments.");
|
||||||
|
return argCount;
|
||||||
|
}
|
||||||
|
|
||||||
long resolveLocal(Token* name){
|
int resolveLocal(Token* name){
|
||||||
for(long i = compiler.locals.count - 1; i >= 0; i--){
|
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
|
||||||
Compiler.Local* local = &compiler.locals[i];
|
currentCompiler.Local* local = ¤tCompiler.locals[i];
|
||||||
if(name.lexeme == local.name.lexeme){
|
if(name.lexeme == local.name.lexeme){
|
||||||
if(local.depth == Compiler.Local.Uninitialised)
|
if(local.depth == currentCompiler.Local.Uninitialised)
|
||||||
error("Can't read local variable in its own initialiser.");
|
error("Can't read local variable in its own initialiser.");
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
@ -263,16 +316,16 @@ struct Parser{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
void addLocal(Token name){
|
void addLocal(Token name){
|
||||||
compiler.locals ~= Compiler.Local(name, Compiler.Local.Uninitialised);
|
currentCompiler.locals ~= currentCompiler.Local(name, currentCompiler.Local.Uninitialised);
|
||||||
}
|
}
|
||||||
void beginScope(){
|
void beginScope(){
|
||||||
compiler.scopeDepth++;
|
currentCompiler.scopeDepth++;
|
||||||
}
|
}
|
||||||
void endScope(){
|
void endScope(){
|
||||||
assert(--compiler.scopeDepth >= 0);
|
assert(--currentCompiler.scopeDepth >= 0);
|
||||||
while(compiler.locals.count > 0 && compiler.locals[compiler.locals.count - 1].depth > compiler.scopeDepth){
|
while(currentCompiler.locals.count > 0 && currentCompiler.locals[currentCompiler.locals.count - 1].depth > currentCompiler.scopeDepth){
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
currentCompiler.emit(OpCode.Pop);
|
||||||
compiler.locals.count--;
|
currentCompiler.locals.count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,11 +337,11 @@ struct Parser{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool canAssign = precedence <= Precedence.Assignment;
|
bool canAssign = precedence <= Precedence.Assignment;
|
||||||
prefixRule(compiler, canAssign);
|
prefixRule(currentCompiler, canAssign);
|
||||||
while(precedence <= ParseRule.get(current.type).precedence){
|
while(precedence <= ParseRule.get(current.type).precedence){
|
||||||
advance();
|
advance();
|
||||||
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
||||||
infixRule(compiler);
|
infixRule(currentCompiler);
|
||||||
}
|
}
|
||||||
if(canAssign && match(Token.Type.Equal))
|
if(canAssign && match(Token.Type.Equal))
|
||||||
error("Invalid assignment target.");
|
error("Invalid assignment target.");
|
||||||
|
|
|
||||||
|
|
@ -4,43 +4,43 @@ import clox.chunk;
|
||||||
import clox.compiler;
|
import clox.compiler;
|
||||||
import clox.parser;
|
import clox.parser;
|
||||||
import clox.scanner;
|
import clox.scanner;
|
||||||
import clox.emitter;
|
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.object;
|
import clox.object;
|
||||||
|
import clox.container.varint;
|
||||||
|
|
||||||
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);
|
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);
|
||||||
|
|
||||||
private void grouping(Compiler* compiler, bool _){
|
private void grouping(Compiler* compiler, bool _){
|
||||||
compiler.parser.expression();
|
parser.expression();
|
||||||
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
||||||
}
|
}
|
||||||
private void unary(Compiler* compiler, bool _){
|
private void unary(Compiler* compiler, bool _){
|
||||||
Token operator = compiler.parser.previous;
|
Token operator = parser.previous;
|
||||||
compiler.parser.parsePrecedence(Precedence.Unary);
|
parser.parsePrecedence(Precedence.Unary);
|
||||||
switch(operator.type){
|
switch(operator.type){
|
||||||
case Token.Type.Minus: compiler.emitter.emit(OpCode.Negate); break;
|
case Token.Type.Minus: compiler.emit(OpCode.Negate); break;
|
||||||
case Token.Type.Bang: compiler.emitter.emit(OpCode.Not); break;
|
case Token.Type.Bang: compiler.emit(OpCode.Not); break;
|
||||||
default: assert(0);
|
default: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void binary(Compiler* compiler, bool _){
|
private void binary(Compiler* compiler, bool _){
|
||||||
Token operator = compiler.parser.previous;
|
Token operator = parser.previous;
|
||||||
immutable(ParseRule)* rule = ParseRule.get(operator.type);
|
immutable(ParseRule)* rule = ParseRule.get(operator.type);
|
||||||
compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1));
|
parser.parsePrecedence(cast(Precedence)(rule.precedence + 1));
|
||||||
switch(operator.type){
|
switch(operator.type){
|
||||||
case Token.Type.Plus: compiler.emitter.emit(OpCode.Add); break;
|
case Token.Type.Plus: compiler.emit(OpCode.Add); break;
|
||||||
case Token.Type.Minus: compiler.emitter.emit(OpCode.Subtract); break;
|
case Token.Type.Minus: compiler.emit(OpCode.Subtract); break;
|
||||||
case Token.Type.Star: compiler.emitter.emit(OpCode.Multiply); break;
|
case Token.Type.Star: compiler.emit(OpCode.Multiply); break;
|
||||||
case Token.Type.Slash: compiler.emitter.emit(OpCode.Divide); break;
|
case Token.Type.Slash: compiler.emit(OpCode.Divide); break;
|
||||||
|
|
||||||
case Token.Type.BangEqual: compiler.emitter.emit(OpCode.NotEqual); break;
|
case Token.Type.BangEqual: compiler.emit(OpCode.NotEqual); break;
|
||||||
case Token.Type.EqualEqual: compiler.emitter.emit(OpCode.Equal); break;
|
case Token.Type.EqualEqual: compiler.emit(OpCode.Equal); break;
|
||||||
|
|
||||||
case Token.Type.Greater: compiler.emitter.emit(OpCode.Greater); break;
|
case Token.Type.Greater: compiler.emit(OpCode.Greater); break;
|
||||||
case Token.Type.GreaterEqual: compiler.emitter.emit(OpCode.GreaterEqual); break;
|
case Token.Type.GreaterEqual: compiler.emit(OpCode.GreaterEqual); break;
|
||||||
|
|
||||||
case Token.Type.Less: compiler.emitter.emit(OpCode.Less); break;
|
case Token.Type.Less: compiler.emit(OpCode.Less); break;
|
||||||
case Token.Type.LessEqual: compiler.emitter.emit(OpCode.LessEqual); break;
|
case Token.Type.LessEqual: compiler.emit(OpCode.LessEqual); break;
|
||||||
|
|
||||||
default: assert(0);
|
default: assert(0);
|
||||||
}
|
}
|
||||||
|
|
@ -48,45 +48,49 @@ private void binary(Compiler* compiler, bool _){
|
||||||
|
|
||||||
private void number(Compiler* compiler, bool _){
|
private void number(Compiler* compiler, bool _){
|
||||||
import core.stdc.stdlib : strtod;
|
import core.stdc.stdlib : strtod;
|
||||||
Token token = compiler.parser.previous;
|
Token token = parser.previous;
|
||||||
double value = strtod(token.lexeme.ptr, null);
|
double value = strtod(token.lexeme.ptr, null);
|
||||||
compiler.emitter.emitConstant(Value.from(value));
|
compiler.emitConstant(Value.from(value));
|
||||||
}
|
}
|
||||||
private void literal(Compiler* compiler, bool _){
|
private void literal(Compiler* compiler, bool _){
|
||||||
Token token = compiler.parser.previous;
|
Token token = parser.previous;
|
||||||
switch(token.type){
|
switch(token.type){
|
||||||
case Token.Type.True: compiler.emitter.emit(OpCode.True); break;
|
case Token.Type.True: compiler.emit(OpCode.True); break;
|
||||||
case Token.Type.False: compiler.emitter.emit(OpCode.False); break;
|
case Token.Type.False: compiler.emit(OpCode.False); break;
|
||||||
case Token.Type.Nil: compiler.emitter.emit(OpCode.Nil); break;
|
case Token.Type.Nil: compiler.emit(OpCode.Nil); break;
|
||||||
default: assert(0);
|
default: assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void strlit(Compiler* compiler, bool _){
|
private void strlit(Compiler* compiler, bool _){
|
||||||
Token token = compiler.parser.previous;
|
Token token = parser.previous;
|
||||||
const char[] 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.emitConstant(Value.from(strObj));
|
compiler.emitConstant(Value.from(strObj));
|
||||||
}
|
}
|
||||||
private void variable(Compiler* compiler, bool canAssign){
|
private void variable(Compiler* compiler, bool canAssign){
|
||||||
compiler.parser.namedVariable(compiler.parser.previous, canAssign);
|
parser.namedVariable(parser.previous, canAssign);
|
||||||
}
|
}
|
||||||
private void and(Compiler* compiler, bool _){
|
private void and(Compiler* compiler, bool _){
|
||||||
int endJump = compiler.parser.emitJump(OpCode.JumpIfFalse);
|
int endJump = parser.emitJump(OpCode.JumpIfFalse);
|
||||||
|
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
compiler.emit(OpCode.Pop);
|
||||||
compiler.parser.parsePrecedence(Precedence.And);
|
parser.parsePrecedence(Precedence.And);
|
||||||
|
|
||||||
compiler.parser.patchJump(endJump);
|
parser.patchJump(endJump);
|
||||||
}
|
}
|
||||||
private void or(Compiler* compiler, bool _){
|
private void or(Compiler* compiler, bool _){
|
||||||
int elseJump = compiler.parser.emitJump(OpCode.JumpIfFalse);
|
int elseJump = parser.emitJump(OpCode.JumpIfFalse);
|
||||||
int endJump = compiler.parser.emitJump(OpCode.Jump);
|
int endJump = parser.emitJump(OpCode.Jump);
|
||||||
|
|
||||||
compiler.parser.patchJump(elseJump);
|
parser.patchJump(elseJump);
|
||||||
compiler.emitter.emit(OpCode.Pop);
|
compiler.emit(OpCode.Pop);
|
||||||
|
|
||||||
compiler.parser.parsePrecedence(Precedence.Or);
|
parser.parsePrecedence(Precedence.Or);
|
||||||
compiler.parser.patchJump(endJump);
|
parser.patchJump(endJump);
|
||||||
|
}
|
||||||
|
private void call(Compiler* compiler, bool _){
|
||||||
|
VarUint argCount = VarUint(parser.argumentList());
|
||||||
|
compiler.emit(OpCode.Call, argCount.bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -114,7 +118,7 @@ enum Precedence{
|
||||||
}
|
}
|
||||||
|
|
||||||
immutable ParseRule[Token.Type.max+1] rules = [
|
immutable ParseRule[Token.Type.max+1] rules = [
|
||||||
Token.Type.LeftParen : ParseRule(&grouping, null, Precedence.None),
|
Token.Type.LeftParen : ParseRule(&grouping, &call, Precedence.Call),
|
||||||
Token.Type.RightParen : ParseRule(null, null, Precedence.None),
|
Token.Type.RightParen : ParseRule(null, null, Precedence.None),
|
||||||
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
|
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
|
||||||
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ struct Token {
|
||||||
int line;
|
int line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Scanner scanner;
|
||||||
|
|
||||||
bool isDigit(char c) => c >= '0' && c <= '9';
|
bool isDigit(char c) => c >= '0' && c <= '9';
|
||||||
bool isAlpha(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
bool isAlpha(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ module clox.util;
|
||||||
|
|
||||||
import core.stdc.stdio;
|
import core.stdc.stdio;
|
||||||
import core.stdc.stdlib;
|
import core.stdc.stdlib;
|
||||||
|
import core.stdc.string;
|
||||||
import std.functional : ctEval;
|
import std.functional : ctEval;
|
||||||
|
|
||||||
enum Colour : string{
|
enum Colour : string{
|
||||||
|
|
@ -12,10 +13,10 @@ string colour(string text, string r, string g, string b)() => ctEval!("\033[38;2
|
||||||
|
|
||||||
extern(C) int isatty(int);
|
extern(C) int isatty(int);
|
||||||
|
|
||||||
static char* readFile(const char* path) {
|
static char[] readFile(const(char)[] path){
|
||||||
FILE* file = path.fopen("rb");
|
FILE* file = path.ptr.fopen("rb");
|
||||||
if(file == null){
|
if(file == null){
|
||||||
stderr.fprintf("Could not open file \"%s\".\n", path);
|
stderr.fprintf("Could not open file \"%s\".\n", path.ptr);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
scope(exit)
|
scope(exit)
|
||||||
|
|
@ -29,7 +30,7 @@ static char* readFile(const char* path) {
|
||||||
size_t bytesRead = fread(buffer, char.sizeof, fileSize, file);
|
size_t bytesRead = fread(buffer, char.sizeof, fileSize, file);
|
||||||
buffer[bytesRead] = '\0';
|
buffer[bytesRead] = '\0';
|
||||||
|
|
||||||
return buffer;
|
return buffer[0 .. bytesRead];
|
||||||
}
|
}
|
||||||
|
|
||||||
char* readStdin(){
|
char* readStdin(){
|
||||||
|
|
@ -52,4 +53,6 @@ char* readStdin(){
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const(char)[] parse(const char* cstr) => cstr[0 .. cstr.strlen];
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
185
src/clox/vm.d
185
src/clox/vm.d
|
|
@ -9,70 +9,87 @@ import clox.object;
|
||||||
import clox.value;
|
import clox.value;
|
||||||
import clox.object;
|
import clox.object;
|
||||||
import clox.compiler;
|
import clox.compiler;
|
||||||
|
import clox.scanner;
|
||||||
import clox.memory;
|
import clox.memory;
|
||||||
import clox.container.stack;
|
|
||||||
import clox.container.table;
|
import clox.container.table;
|
||||||
import clox.container.varint;
|
import clox.container.varint;
|
||||||
|
|
||||||
VM vm;
|
VM vm;
|
||||||
|
|
||||||
struct VM{
|
struct VM{
|
||||||
Chunk* chunk;
|
Value[1024] stack;
|
||||||
ubyte* ip;
|
Value* stackTop;
|
||||||
Stack!Value stack;
|
CallFrame[256] frames;
|
||||||
|
uint frameCount;
|
||||||
Table globals;
|
Table globals;
|
||||||
Table strings;
|
Table strings;
|
||||||
Obj* objects;
|
Obj* objects;
|
||||||
bool isREPL;
|
bool isREPL;
|
||||||
|
debug(printCode) bool printCode;
|
||||||
|
debug(traceExec) bool traceExec;
|
||||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||||
|
struct CallFrame{
|
||||||
|
Obj.Function* func;
|
||||||
|
ubyte* ip;
|
||||||
|
Value* slots;
|
||||||
|
}
|
||||||
void initialise(){
|
void initialise(){
|
||||||
stack.initialise();
|
|
||||||
strings.initialise();
|
strings.initialise();
|
||||||
globals.initialise();
|
globals.initialise();
|
||||||
|
stackTop = &stack[0];
|
||||||
|
|
||||||
|
defineNative("clock", (int argCount, Value* args){
|
||||||
|
import core.stdc.time;
|
||||||
|
return Value.from(cast(double)clock() / CLOCKS_PER_SEC);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
void free(){
|
void free(){
|
||||||
freeObjects();
|
freeObjects();
|
||||||
strings.free();
|
strings.free();
|
||||||
globals.free();
|
globals.free();
|
||||||
stack.free();
|
}
|
||||||
|
void push(Value v){
|
||||||
|
*(stackTop++) = v;
|
||||||
|
}
|
||||||
|
Value pop(){
|
||||||
|
return *(--stackTop);
|
||||||
}
|
}
|
||||||
InterpretResult interpret(const char* source){
|
InterpretResult interpret(const char* source){
|
||||||
Chunk cnk;
|
scanner.initialise(source);
|
||||||
cnk.initialise();
|
|
||||||
scope(exit)
|
|
||||||
cnk.free();
|
|
||||||
Compiler compiler;
|
Compiler compiler;
|
||||||
if(!compiler.compile(source, &cnk))
|
compiler.initialise(Compiler.FunctionType.Script);
|
||||||
|
Obj.Function* func = compiler.compile();
|
||||||
|
if(func is null)
|
||||||
return InterpretResult.CompileError;
|
return InterpretResult.CompileError;
|
||||||
this.chunk = &cnk;
|
push(Value.from(func));
|
||||||
this.ip = cnk.code.ptr;
|
call(func, 0);
|
||||||
return run();
|
return run();
|
||||||
}
|
}
|
||||||
void runtimeError(Args...)(const char* format, Args args){
|
void runtimeError(Args...)(const char* format, Args args){
|
||||||
fprintf(stderr, format, args);
|
fprintf(stderr, format, args);
|
||||||
fputs("\n", stderr);
|
fputs("\n", stderr);
|
||||||
|
printStackTrace();
|
||||||
size_t instruction = ip - chunk.code.ptr - 1;
|
|
||||||
uint line = chunk.lines[instruction];
|
|
||||||
fprintf(stderr, "[line %d] in script\n", line);
|
|
||||||
}
|
}
|
||||||
InterpretResult run(){
|
InterpretResult run(){
|
||||||
debug(traceExec){
|
CallFrame* frame = &frames[frameCount - 1];
|
||||||
printHeader(chunk);
|
ref ip() => frame.ip;
|
||||||
|
ref chunk() => frame.func.chunk;
|
||||||
|
debug(traceExec) if(traceExec){
|
||||||
|
printHeader(&chunk());
|
||||||
}
|
}
|
||||||
ubyte readByte() => *ip++;
|
ubyte readByte() => *ip++;
|
||||||
ushort readShort(){
|
ushort readShort(){
|
||||||
ip += 2;
|
ip += 2;
|
||||||
return (ip[-2] << 8) | ip[-1];
|
return (ip[-2] << 8) | ip[-1];
|
||||||
}
|
}
|
||||||
long readVarUint(){
|
int readVarUint(){
|
||||||
VarUint vi = VarUint.read(ip);
|
VarUint vi = VarUint.read(ip);
|
||||||
ip += vi.len;
|
ip += vi.len;
|
||||||
return vi.i;
|
return vi.i;
|
||||||
}
|
}
|
||||||
Value readConstant() => chunk.constants[readVarUint()];
|
Value readConstant() => chunk.constants[readVarUint()];
|
||||||
Obj.String* readString() => readConstant().asObj.asString;
|
Obj.String* readString() => readConstant().asObj.asString;
|
||||||
Value peek(int distance = 0) => stack.top[-1 - distance];
|
Value peek(int distance = 0) => stackTop[-1 - distance];
|
||||||
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
||||||
bool checkSameType()() => peek(0).type == peek(1).type;
|
bool checkSameType()() => peek(0).type == peek(1).type;
|
||||||
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||||
|
|
@ -80,32 +97,34 @@ struct VM{
|
||||||
runtimeError(checkMsg.ptr);
|
runtimeError(checkMsg.ptr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
auto b = stack.pop().as!pre;
|
auto b = pop().as!pre;
|
||||||
auto a = stack.pop().as!pre;
|
auto a = pop().as!pre;
|
||||||
stack ~= Value.from(mixin("a", op, "b"));
|
push(Value.from(mixin("a", op, "b")));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
while(true){
|
while(true){
|
||||||
debug(traceExec){
|
debug(traceExec) if(traceExec){
|
||||||
disassembleInstruction(chunk, ip - chunk.code.ptr);
|
disassembleInstruction(&chunk(), ip - chunk.code.ptr);
|
||||||
|
printf(colour!(" [", Colour.Black).ptr);
|
||||||
|
for(Value* v = stack.ptr+1; v < stackTop; v++){
|
||||||
printf(" ");
|
printf(" ");
|
||||||
foreach(slot; stack.live){
|
v.print();
|
||||||
printf("");
|
if(v+1 < stackTop)
|
||||||
slot.print();
|
|
||||||
printf(colour!(",", Colour.Black).ptr);
|
printf(colour!(",", Colour.Black).ptr);
|
||||||
|
printf(" ");
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf(colour!("]\n", Colour.Black).ptr);
|
||||||
}
|
}
|
||||||
OpCode instruction;
|
OpCode instruction;
|
||||||
opSwitch: final switch(instruction = cast(OpCode)readByte()){
|
opSwitch: final switch(instruction = cast(OpCode)readByte()){
|
||||||
case OpCode.Constant:
|
case OpCode.Constant:
|
||||||
Value constant = readConstant();
|
Value constant = readConstant();
|
||||||
stack ~= constant;
|
push(constant);
|
||||||
break;
|
break;
|
||||||
case OpCode.Nil: stack ~= Value.nil; break;
|
case OpCode.Nil: push(Value.nil); break;
|
||||||
case OpCode.True: stack ~= Value.from(true); break;
|
case OpCode.True: push(Value.from(true)); break;
|
||||||
case OpCode.False: stack ~= Value.from(false); break;
|
case OpCode.False: push(Value.from(false)); break;
|
||||||
case OpCode.Pop: stack.pop(); break;
|
case OpCode.Pop: pop(); break;
|
||||||
|
|
||||||
case OpCode.Jump:
|
case OpCode.Jump:
|
||||||
ushort offset = readShort();
|
ushort offset = readShort();
|
||||||
|
|
@ -123,11 +142,11 @@ struct VM{
|
||||||
|
|
||||||
case OpCode.GetLocal:
|
case OpCode.GetLocal:
|
||||||
long slot = readVarUint();
|
long slot = readVarUint();
|
||||||
stack ~= stack[slot];
|
push(frame.slots[slot]);
|
||||||
break;
|
break;
|
||||||
case OpCode.SetLocal:
|
case OpCode.SetLocal:
|
||||||
long slot = readVarUint();
|
long slot = readVarUint();
|
||||||
stack[slot] = peek(0);
|
frame.slots[slot] = peek(0);
|
||||||
break;
|
break;
|
||||||
case OpCode.GetGlobal:
|
case OpCode.GetGlobal:
|
||||||
Obj.String* name = readString();
|
Obj.String* name = readString();
|
||||||
|
|
@ -136,12 +155,12 @@ struct VM{
|
||||||
runtimeError("Undefined variable '%s'.", name.chars.ptr);
|
runtimeError("Undefined variable '%s'.", name.chars.ptr);
|
||||||
return InterpretResult.RuntimeError;
|
return InterpretResult.RuntimeError;
|
||||||
}
|
}
|
||||||
stack ~= value;
|
push(value);
|
||||||
break;
|
break;
|
||||||
case OpCode.DefineGlobal:
|
case OpCode.DefineGlobal:
|
||||||
Obj.String* name = readString();
|
Obj.String* name = readString();
|
||||||
globals.set(name, peek(0));
|
globals.set(name, peek(0));
|
||||||
stack.pop();
|
pop();
|
||||||
break;
|
break;
|
||||||
case OpCode.SetGlobal:
|
case OpCode.SetGlobal:
|
||||||
Obj.String* name = readString();
|
Obj.String* name = readString();
|
||||||
|
|
@ -164,14 +183,14 @@ struct VM{
|
||||||
break opSwitch;
|
break opSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
static foreach(k, op; [ OpCode.Add: "+", OpCode.Subtract: "-", OpCode.Multiply: "*", OpCode.Divide: "-" ]){
|
static foreach(k, op; [ OpCode.Add: "+", OpCode.Subtract: "-", OpCode.Multiply: "*", OpCode.Divide: "/" ]){
|
||||||
case k:
|
case k:
|
||||||
static if(k == OpCode.Add){
|
static if(k == OpCode.Add){
|
||||||
if(checkBinaryType!(Obj.Type.String)){
|
if(checkBinaryType!(Obj.Type.String)){
|
||||||
Obj.String* b = stack.pop().asObj.asString;
|
Obj.String* b = pop().asObj.asString;
|
||||||
Obj.String* a = stack.pop().asObj.asString;
|
Obj.String* a = pop().asObj.asString;
|
||||||
Obj.String* result = Obj.String.concat(a, b);
|
Obj.String* result = Obj.String.concat(a, b);
|
||||||
stack ~= Value.from(result);
|
push(Value.from(result));
|
||||||
break opSwitch;
|
break opSwitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,24 +200,98 @@ struct VM{
|
||||||
}
|
}
|
||||||
|
|
||||||
case OpCode.Not:
|
case OpCode.Not:
|
||||||
stack ~= Value.from(stack.pop().isFalsey);
|
push(Value.from(pop().isFalsey));
|
||||||
break;
|
break;
|
||||||
case OpCode.Negate:
|
case OpCode.Negate:
|
||||||
if(!peek(0).isType(Value.Type.Number)){
|
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 ~= Value.from(-stack.pop().asNumber);
|
push(Value.from(-pop().asNumber));
|
||||||
break;
|
break;
|
||||||
case OpCode.Print:
|
case OpCode.Print:
|
||||||
stack.pop().print("%s");
|
pop().print("%s");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OpCode.Call:
|
||||||
|
int argCount = readVarUint();
|
||||||
|
if(!callValue(peek(argCount), argCount))
|
||||||
|
return InterpretResult.RuntimeError;
|
||||||
|
frame = &frames[frameCount - 1];
|
||||||
|
break;
|
||||||
case OpCode.Return:
|
case OpCode.Return:
|
||||||
|
Value result = pop();
|
||||||
|
vm.frameCount--;
|
||||||
|
if(vm.frameCount == 0){
|
||||||
|
pop();
|
||||||
return InterpretResult.Ok;
|
return InterpretResult.Ok;
|
||||||
}
|
}
|
||||||
|
stackTop = frame.slots;
|
||||||
|
push(result);
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool call(Obj.Function* func, int argCount){
|
||||||
|
if(argCount != func.arity){
|
||||||
|
runtimeError("Expected %d arguments but got %d.", func.arity, argCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(frameCount == frames.length){
|
||||||
|
runtimeError("Stack overflow.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CallFrame* frame = &frames[frameCount++];
|
||||||
|
frame.func = func;
|
||||||
|
frame.ip = func.chunk.code.ptr;
|
||||||
|
frame.slots = stackTop - argCount - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool callValue(Value callee, int argCount){
|
||||||
|
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.NativeFunction:
|
||||||
|
Obj.NativeFunction* native = obj.asNativeFunc;
|
||||||
|
Value result = native.func(argCount, vm.stackTop - argCount);
|
||||||
|
vm.stackTop -= argCount + 1;
|
||||||
|
push(result);
|
||||||
|
return true;
|
||||||
|
case Obj.Type.String: break;
|
||||||
|
case Obj.Type.None: assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimeError("Can only call functions and classes.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printStackTrace(){
|
||||||
|
for(int i = vm.frameCount - 1; i >= 0; i--){
|
||||||
|
CallFrame* frame = &frames[i];
|
||||||
|
Obj.Function* func = frame.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){
|
||||||
|
fprintf(stderr, "script\n");
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%s()\n", func.name.chars.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void defineNative(const(char)[] name, Obj.NativeFunction.Sig func){
|
||||||
|
push(Value.from(Obj.String.copy(name)));
|
||||||
|
push(Value.from(Obj.NativeFunction.create(func)));
|
||||||
|
globals.set(stack[0].asObj.asString, vm.stack[1]);
|
||||||
|
pop();
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
test/all.d
29
test/all.d
|
|
@ -8,27 +8,36 @@ import std.string, std.format;
|
||||||
import std.algorithm, std.range;
|
import std.algorithm, std.range;
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
|
|
||||||
|
"./test/func.lox".match("-1\n6\n1\n");
|
||||||
|
"./test/bigsum.lox".match("125250\n");
|
||||||
|
"./test/biglocals.lox".match("125250\n");
|
||||||
|
"./test/simplescope.lox".match("abc\nabd\n");
|
||||||
|
"./test/for.lox".match("1 2 1 2 3 ".replace(' ', '\n'));
|
||||||
|
"./test/ifelse.lox".match("a1 b2 c3 ".replace(' ', '\n'));
|
||||||
|
"./test/while.lox".match("1 2 3 2 1 0 1 1 2 3 2 1 0 1 ".replace(' ', '\n'));
|
||||||
|
|
||||||
"./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n");
|
"./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n");
|
||||||
"./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n");
|
"./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n");
|
||||||
"./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').repeat(2).join("\n"));
|
"./test/scope.lox".match("global first first second first ".replace(' ', '\n'));
|
||||||
"./test/fib_for.lox".match(fib(6765));
|
"./test/fib_for.lox".match(fib(6765));
|
||||||
"./test/fib_recursive.lox".match(fib(34));
|
"./test/fib_recursive.lox".match(fib(34));
|
||||||
"./test/fib_closure.lox".match(fib(34));
|
/* "./test/fib_closure.lox".match(fib(34)); */
|
||||||
"./test/class.lox".match("The German chocolate cake is delicious!\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");
|
/* "./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n"); */
|
||||||
|
|
||||||
"./test/err/invalid_syntax.lox".shouldFail(RetVal.other);
|
"./test/err/invalid_syntax.lox".shouldFail(RetVal.other);
|
||||||
"./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name");
|
"./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name");
|
||||||
"./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable");
|
"./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable");
|
||||||
"./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable");
|
"./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable");
|
||||||
"./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code");
|
"./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code");
|
||||||
"./test/err/super_outside_class.lox".shouldFail(RetVal.other, "Can't use 'super' outside of a class");
|
/* "./test/err/super_outside_class.lox".shouldFail(RetVal.other, "Can't use 'super' outside of a class"); */
|
||||||
"./test/err/super_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass");
|
/* "./test/err/super_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass"); */
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RetVal{
|
enum RetVal{
|
||||||
success = 0, other = 1, runtime = 2
|
success = 0, other = 65, runtime = 70
|
||||||
}
|
}
|
||||||
|
|
||||||
string fib(uint n){
|
string fib(uint n){
|
||||||
|
|
@ -42,13 +51,13 @@ string fib(uint n){
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
auto run(string file) => [ "./lox", file ].execute;
|
auto run(string file, Config.Flags f = Config.Flags.stderrPassThrough) => [ "./lox", file ].execute(null, Config(f));
|
||||||
void match(string file, string correct){
|
void match(string file, string correct){
|
||||||
auto res = file.run.output;
|
auto res = file.run.output;
|
||||||
assert(res == correct, "Match %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, res, correct));
|
assert(res == correct, "Match %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, res, correct));
|
||||||
}
|
}
|
||||||
void shouldFail(string file, int code = 1, string msg = null){
|
void shouldFail(string file, int code = 1, string msg = null){
|
||||||
auto c = file.run;
|
auto c = file.run(Config.Flags.none);
|
||||||
assert(c.status == code, "Expected %s to fail with code %d but got %d".format(file, code, c.status));
|
assert(c.status == code, "Expected %s to fail with code %d but got %d".format(file, code, c.status));
|
||||||
assert(!msg || c.output.toLower.indexOf(msg.toLower) >= 0, "ShouldFail %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, c.output, msg));
|
assert(!msg || c.output.toLower.indexOf(msg.toLower) >= 0, "ShouldFail %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, c.output, msg));
|
||||||
assert(c.output.indexOf("_Dmain") == -1, "ShouldFail %s got D exception\n%s".format(file, c.output));
|
assert(c.output.indexOf("_Dmain") == -1, "ShouldFail %s got D exception\n%s".format(file, c.output));
|
||||||
|
|
|
||||||
13
test/func.lox
Normal file
13
test/func.lox
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
fun sub(a, b){
|
||||||
|
print a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mul(a, b){
|
||||||
|
sub(a, b);
|
||||||
|
print a * b;
|
||||||
|
sub(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
mul(2, 3);
|
||||||
|
|
||||||
|
|
@ -14,6 +14,4 @@ fun f(){
|
||||||
}
|
}
|
||||||
|
|
||||||
f();
|
f();
|
||||||
print "";
|
|
||||||
f();
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue