Calls and Functions 24

This commit is contained in:
nazrin 2025-06-09 03:54:01 +00:00
parent 2cd7a44bc7
commit a45f6a9e17
18 changed files with 550 additions and 285 deletions

19
dub.sdl
View file

@ -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"
} }

View file

@ -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();

View file

@ -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;
} }
} }

View file

@ -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];
}
} }

View file

@ -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();
}
}

View file

@ -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);
} }

View file

@ -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;
}
}

View file

@ -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;
} }
} }

View file

@ -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.lineStack ~= lineno; btb.fileStack ~= filename.to!string;
btb.funcStack ~= func.to!string.demangle.stripLeft("nothrow ").stripLeft("@nogc ");
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);
} }

View file

@ -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);
} }
} }

View file

@ -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 = &currentCompiler.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 = &currentCompiler.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.");

View file

@ -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),

View file

@ -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 == '_';

View file

@ -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];

View file

@ -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(" "); printf(colour!(" [", Colour.Black).ptr);
foreach(slot; stack.live){ for(Value* v = stack.ptr+1; v < stackTop; v++){
printf(""); printf(" ");
slot.print(); v.print();
printf(colour!(", ", Colour.Black).ptr); if(v+1 < stackTop)
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:
return InterpretResult.Ok; Value result = pop();
vm.frameCount--;
if(vm.frameCount == 0){
pop();
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();
}
} }

View file

@ -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
View 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);

View file

@ -14,6 +14,4 @@ fun f(){
} }
f(); f();
print "";
f();