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"
|
||||
copyright "Copyright © 2025, tanya"
|
||||
license "MPL-2.0"
|
||||
dependency "commandr" version="~>1.1.0"
|
||||
dependency "colored" version="~>0.0.33"
|
||||
targetType "executable"
|
||||
sourcePaths
|
||||
|
||||
configuration "clox" {
|
||||
buildOptions "betterC"
|
||||
/* debugVersions "memTrace" */
|
||||
/* debugVersions "traceExec" */
|
||||
sourcePaths "src/clox"
|
||||
buildRequirements "requireContracts"
|
||||
}
|
||||
|
||||
configuration "clox-dbg" {
|
||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||
debugVersions "memTrace"
|
||||
debugVersions "printCode"
|
||||
debugVersions "traceExec"
|
||||
dependency "colored" version="~>0.0.33"
|
||||
dflags "-checkaction=C"
|
||||
libs "libbacktrace"
|
||||
targetType "executable"
|
||||
sourcePaths "src/clox"
|
||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||
}
|
||||
|
||||
configuration "jlox" {
|
||||
targetType "executable"
|
||||
dependency "colored" version="~>0.0.33"
|
||||
dependency "commandr" version="~>1.1.0"
|
||||
sourcePaths "src/jlox" "src/common"
|
||||
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
|
||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct OpColour{
|
|||
enum OpConst;
|
||||
enum OpStack;
|
||||
enum OpJump;
|
||||
enum OpCall;
|
||||
|
||||
enum OpCode : ubyte{
|
||||
@OpConst @(OpColour("200", "200", "100")) Constant,
|
||||
|
|
@ -48,7 +49,9 @@ enum OpCode : ubyte{
|
|||
@(OpColour("200", "100", "100")) Not,
|
||||
@(OpColour("100", "100", "200")) Negate,
|
||||
@(OpColour("000", "200", "100")) Print,
|
||||
@(OpColour("000", "200", "100")) Return,
|
||||
|
||||
@OpCall @(OpColour("250", "200", "250")) Call,
|
||||
@(OpColour("250", "190", "200")) Return,
|
||||
}
|
||||
|
||||
struct Chunk{
|
||||
|
|
@ -68,12 +71,12 @@ struct Chunk{
|
|||
code ~= b;
|
||||
lines ~= line;
|
||||
}
|
||||
size_t addConstant(Value value){
|
||||
long index = constants[].countUntil(value);
|
||||
int addConstant(Value value){
|
||||
int index = cast(int)constants[].countUntil(value);
|
||||
if(index >= 0)
|
||||
return index;
|
||||
constants ~= value;
|
||||
return constants.count - 1;
|
||||
return cast(int)constants.count - 1;
|
||||
}
|
||||
void free(){
|
||||
code.free();
|
||||
|
|
|
|||
|
|
@ -5,14 +5,21 @@ import core.stdc.stdio;
|
|||
import clox.scanner;
|
||||
import clox.parser;
|
||||
import clox.chunk;
|
||||
import clox.emitter;
|
||||
import clox.dbg;
|
||||
import clox.vm;
|
||||
import clox.object;
|
||||
import clox.value;
|
||||
import clox.container.dynarray;
|
||||
import clox.container.varint;
|
||||
|
||||
Parser parser;
|
||||
Compiler* currentCompiler;
|
||||
|
||||
struct Compiler{
|
||||
Scanner scanner;
|
||||
Parser parser;
|
||||
Emitter emitter;
|
||||
Compiler* enclosing;
|
||||
Obj.Function* func;
|
||||
enum FunctionType{ None, Function, Script }
|
||||
FunctionType ftype = FunctionType.Script;
|
||||
struct Local{
|
||||
enum Uninitialised = -1;
|
||||
Token name;
|
||||
|
|
@ -21,28 +28,58 @@ struct Compiler{
|
|||
DynArray!Local locals;
|
||||
int scopeDepth;
|
||||
private Chunk* compilingChunk;
|
||||
Chunk* currentChunk() => compilingChunk;
|
||||
bool compile(const(char)* source, Chunk* chunk){
|
||||
scanner.initialise(source);
|
||||
parser.initialise(&this);
|
||||
emitter.initialise(&this);
|
||||
Chunk* currentChunk() => &func.chunk;
|
||||
void initialise(FunctionType ftype){
|
||||
this.ftype = ftype;
|
||||
enclosing = currentCompiler;
|
||||
currentCompiler = &this;
|
||||
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();
|
||||
while(!parser.match(Token.Type.EOF)){
|
||||
parser.declaration();
|
||||
}
|
||||
end();
|
||||
return !parser.hadError;
|
||||
|
||||
Obj.Function* f = end();
|
||||
return parser.hadError ? null : f;
|
||||
}
|
||||
void end(){
|
||||
emitter.emitReturn();
|
||||
debug(printCode){
|
||||
Obj.Function* end(){
|
||||
emitReturn();
|
||||
Obj.Function* f = func;
|
||||
debug(printCode) if(vm.printCode){
|
||||
if(!parser.hadError)
|
||||
currentChunk.disassembleChunk();
|
||||
currentChunk.disassembleChunk(func.name !is null ? func.name.chars.ptr : "<script>");
|
||||
}
|
||||
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;
|
||||
|
||||
struct DynArray(T){
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
private size_t _count;
|
||||
private size_t _capacity;
|
||||
ref size_t count() => _count;
|
||||
size_t capacity() => _capacity;
|
||||
// TODO capacity setter
|
||||
T* ptr;
|
||||
void initialise(){
|
||||
count = 0;
|
||||
capacity = 0;
|
||||
_count = 0;
|
||||
_capacity = 0;
|
||||
ptr = null;
|
||||
}
|
||||
void opOpAssign(string op: "~")(in T value){
|
||||
if(capacity < count + 1){
|
||||
size_t oldCapacity = capacity;
|
||||
capacity = GROW_CAPACITY(oldCapacity);
|
||||
_capacity = GROW_CAPACITY(oldCapacity);
|
||||
ptr = GROW_ARRAY!T(ptr, oldCapacity, capacity);
|
||||
}
|
||||
ptr[count] = value;
|
||||
count++;
|
||||
_count++;
|
||||
}
|
||||
auto opSlice(){
|
||||
ref auto opSlice(){
|
||||
assert(ptr || !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);
|
||||
return ptr[i .. o];
|
||||
}
|
||||
|
|
@ -35,15 +38,19 @@ struct DynArray(T){
|
|||
size_t opDollar(size_t pos: 0)(){
|
||||
return count;
|
||||
}
|
||||
auto pop(){
|
||||
assert(count);
|
||||
return this[--_count];
|
||||
}
|
||||
void reset(){
|
||||
_count = 0;
|
||||
debug foreach(ref item; this[])
|
||||
item = T.init;
|
||||
}
|
||||
void free(){
|
||||
if(ptr)
|
||||
FREE_ARRAY!T(ptr, capacity);
|
||||
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){
|
||||
ushort jump = cast(ushort)(chunk.code[offset + 1] << 8) | chunk.code[offset + 2];
|
||||
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);
|
||||
printf("%i ", sign * jump);
|
||||
long pl = printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4ld ", Colour.Green)).ptr, name, (jump * sign) + offset + 3);
|
||||
pl += printf("%i ", sign * jump);
|
||||
foreach(i; 0 .. 70-pl)
|
||||
printf(" ");
|
||||
return offset + 3;
|
||||
}
|
||||
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(" ");
|
||||
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"){
|
||||
printf(" ============== %s ==============\n", name);
|
||||
|
|
@ -74,6 +83,8 @@ long disassembleInstruction(Chunk* chunk, long offset){
|
|||
case e: return stackIndexInstruction!e(e.stringof, chunk, offset);
|
||||
} else static if(hasUDA!(e, OpJump)){
|
||||
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 {
|
||||
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;
|
||||
|
||||
import std.algorithm;
|
||||
import core.sys.posix.unistd : STDIN_FILENO;
|
||||
import core.stdc.stdio;
|
||||
import core.stdc.stdlib;
|
||||
|
|
@ -35,28 +36,52 @@ int runStdin(){
|
|||
source.free();
|
||||
return vm.interpret(source).interpretResult;
|
||||
}
|
||||
int runFile(const char* path){
|
||||
char* source = path.readFile();
|
||||
int runFile(const char[] path){
|
||||
auto source = path.readFile();
|
||||
if(!source)
|
||||
return 74;
|
||||
scope(exit)
|
||||
source.free();
|
||||
return vm.interpret(source).interpretResult;
|
||||
source.ptr.free();
|
||||
return vm.interpret(source.ptr).interpretResult;
|
||||
}
|
||||
|
||||
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();
|
||||
scope(exit)
|
||||
vm.free();
|
||||
|
||||
if(argc == 1 && isatty(STDIN_FILENO)){
|
||||
if(!file && isatty(STDIN_FILENO)){
|
||||
return runPrompt();
|
||||
} else if(argc == 1){
|
||||
} else if(!file){
|
||||
return runStdin();
|
||||
} else if(argc == 2){
|
||||
return runFile(argv[1]);
|
||||
} else if(file){
|
||||
return runFile(file);
|
||||
} else {
|
||||
stderr.fprintf("Usage: lox [path]\n");
|
||||
usage();
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ module clox.memory;
|
|||
|
||||
import core.stdc.stdlib;
|
||||
|
||||
import clox.util;
|
||||
|
||||
auto GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
|
||||
auto GROW_ARRAY(T)(T* ptr, size_t oldCount, size_t newCount) => cast(T*)reallocate(ptr, T.sizeof * oldCount, T.sizeof * newCount);
|
||||
auto FREE_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
|
||||
|
|
@ -29,9 +31,14 @@ version(D_BetterC) {} else {
|
|||
import std.demangle;
|
||||
BackTraceBuilder* btb = cast(BackTraceBuilder*)data;
|
||||
if(func){
|
||||
string funcStr = func.to!string.demangle;
|
||||
btb.funcStack ~= funcStr;
|
||||
btb.lineStack ~= lineno;
|
||||
import std.string;
|
||||
try{
|
||||
btb.fileStack ~= filename.to!string;
|
||||
btb.funcStack ~= func.to!string.demangle.stripLeft("nothrow ").stripLeft("@nogc ");
|
||||
btb.lineStack ~= lineno;
|
||||
} catch(Exception){
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -41,10 +48,14 @@ version(D_BetterC) {} else {
|
|||
}
|
||||
|
||||
struct BackTraceBuilder{
|
||||
string[] fileStack;
|
||||
string[] funcStack;
|
||||
int[] lineStack;
|
||||
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{
|
||||
|
|
@ -62,7 +73,7 @@ version(D_BetterC) {} else {
|
|||
ulong totalAllocs, totalReallocs, totalFrees;
|
||||
ulong totalAllocBytes, totalFreedBytes;
|
||||
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){
|
||||
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
|
||||
|
||||
|
|
@ -123,8 +134,17 @@ void freeObject(Obj* object){
|
|||
final switch(object.type){
|
||||
case Obj.Type.String:
|
||||
Obj.String* str = cast(Obj.String*)object;
|
||||
FREE_ARRAY!char(str.chars.ptr, str.chars.length + 1);
|
||||
FREE!(Obj.String)(cast(Obj.String*)object);
|
||||
FREE_ARRAY(str.chars.ptr, str.chars.length + 1);
|
||||
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;
|
||||
case Obj.Type.None: assert(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ module clox.object;
|
|||
|
||||
import core.stdc.stdio;
|
||||
|
||||
import clox.chunk;
|
||||
import clox.container.table;
|
||||
import clox.memory;
|
||||
import clox.value;
|
||||
import clox.vm;
|
||||
import clox.container.table;
|
||||
|
||||
struct Obj{
|
||||
enum Type{
|
||||
None, String,
|
||||
None, String, Function, NativeFunction
|
||||
}
|
||||
Type type;
|
||||
Obj* next;
|
||||
|
|
@ -19,17 +20,27 @@ struct Obj{
|
|||
static if(type != Type.None)
|
||||
assert(this.type == type);
|
||||
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;
|
||||
}
|
||||
|
||||
int print(const char* strFmt = `"%s"`) const{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)(){
|
||||
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{
|
||||
if(rhs.type != type)
|
||||
return false;
|
||||
final switch(type){
|
||||
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);
|
||||
}
|
||||
}
|
||||
bool opCmp(in ref Obj rhs) const pure{
|
||||
final switch(type){
|
||||
case Type.String: return asString.chars > rhs.asString.chars;
|
||||
case Type.Function: return false;
|
||||
case Type.NativeFunction: return false;
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +143,8 @@ struct Obj{
|
|||
static string toString(Obj* obj){
|
||||
final switch(obj.type){
|
||||
case Type.String: return '"' ~ obj.asString.chars.idup ~ '"';
|
||||
case Type.Function: return "Function";
|
||||
case Type.NativeFunction: return "NativeFunction";
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,12 @@ import clox.vm;
|
|||
import clox.container.varint;
|
||||
|
||||
struct Parser{
|
||||
Compiler* compiler;
|
||||
Token current, previous;
|
||||
bool hadError, panicMode;
|
||||
void initialise(Compiler* compiler){
|
||||
this.compiler = compiler;
|
||||
}
|
||||
void advance(){
|
||||
previous = current;
|
||||
while(true){
|
||||
current = compiler.scanner.scanToken();
|
||||
current = scanner.scanToken();
|
||||
if(current.type != Token.Type.Error)
|
||||
break;
|
||||
|
||||
|
|
@ -52,28 +48,36 @@ struct Parser{
|
|||
expression();
|
||||
if(vm.isREPL){
|
||||
match(Token.Type.Semicolon);
|
||||
compiler.emitter.emit(OpCode.Print);
|
||||
currentCompiler.emit(OpCode.Print);
|
||||
} else {
|
||||
consume(Token.Type.Semicolon, "Expect ';' after expression.");
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
}
|
||||
}
|
||||
void varDeclaration(){
|
||||
long global = parseVariable("Expect variable name.");
|
||||
int global = parseVariable("Expect variable name.");
|
||||
if(match(Token.Type.Equal)){
|
||||
expression();
|
||||
} else {
|
||||
compiler.emitter.emit(OpCode.Nil);
|
||||
currentCompiler.emit(OpCode.Nil);
|
||||
}
|
||||
consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
|
||||
defineVariable(global);
|
||||
}
|
||||
void funDeclaration(){
|
||||
int global = parseVariable("Expect function name");
|
||||
markInitialised();
|
||||
func();
|
||||
defineVariable(global);
|
||||
}
|
||||
void declaration(){
|
||||
if(match(Token.Type.Var))
|
||||
varDeclaration();
|
||||
else if(match(Token.Type.Fun))
|
||||
funDeclaration();
|
||||
else
|
||||
statement();
|
||||
if(compiler.parser.panicMode)
|
||||
if(parser.panicMode)
|
||||
synchronise();
|
||||
}
|
||||
void statement(){
|
||||
|
|
@ -81,6 +85,8 @@ struct Parser{
|
|||
printStatement();
|
||||
} else if(match(Token.Type.If)){
|
||||
ifStatement();
|
||||
} else if(match(Token.Type.Return)){
|
||||
returnStatement();
|
||||
} else if(match(Token.Type.While)){
|
||||
whileStatement();
|
||||
} else if(match(Token.Type.For)){
|
||||
|
|
@ -95,7 +101,19 @@ struct Parser{
|
|||
void printStatement(){
|
||||
expression();
|
||||
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(){
|
||||
consume(Token.Type.LeftParen, "Expect '(' after 'if'.");
|
||||
|
|
@ -103,13 +121,13 @@ struct Parser{
|
|||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||
|
||||
int thenJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
statement();
|
||||
|
||||
int elseJump = emitJump(OpCode.Jump);
|
||||
|
||||
patchJump(thenJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
|
||||
if(match(Token.Type.Else))
|
||||
statement();
|
||||
|
|
@ -117,18 +135,18 @@ struct Parser{
|
|||
patchJump(elseJump);
|
||||
}
|
||||
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'.");
|
||||
expression();
|
||||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||
|
||||
int exitJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
statement();
|
||||
emitLoop(loopStart);
|
||||
|
||||
patchJump(exitJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
}
|
||||
void forStatement(){
|
||||
beginScope();
|
||||
|
|
@ -141,20 +159,20 @@ struct Parser{
|
|||
expressionStatement();
|
||||
}
|
||||
|
||||
int loopStart = cast(int)compiler.currentChunk.code.count;
|
||||
int loopStart = cast(int)currentCompiler.currentChunk.code.count;
|
||||
int exitJump = -1;
|
||||
if(!match(Token.Type.Semicolon)){
|
||||
expression();
|
||||
consume(Token.Type.Semicolon, "Expect ';' after loop condition.");
|
||||
exitJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
}
|
||||
|
||||
if(!match(Token.Type.RightParen)){
|
||||
int bodyJump = emitJump(OpCode.Jump);
|
||||
int incrementStart = cast(int)compiler.currentChunk.code.count;
|
||||
int incrementStart = cast(int)currentCompiler.currentChunk.code.count;
|
||||
expression();
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
consume(Token.Type.RightParen, "Expect ')' after for clauses.");
|
||||
emitLoop(loopStart);
|
||||
loopStart = incrementStart;
|
||||
|
|
@ -166,7 +184,7 @@ struct Parser{
|
|||
|
||||
if(exitJump != -1){
|
||||
patchJump(exitJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
}
|
||||
|
||||
endScope();
|
||||
|
|
@ -176,65 +194,89 @@ struct Parser{
|
|||
declaration();
|
||||
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){
|
||||
long jump = compiler.currentChunk.code.count - offset - 2;
|
||||
int jump = cast(int)currentCompiler.currentChunk.code.count - offset - 2;
|
||||
if(jump > ushort.max)
|
||||
error("Too much code to jump over.");
|
||||
compiler.currentChunk.code[offset] = (jump >> 8) & 0xff;
|
||||
compiler.currentChunk.code[offset + 1] = jump & 0xff;
|
||||
currentCompiler.currentChunk.code[offset] = (jump >> 8) & 0xff;
|
||||
currentCompiler.currentChunk.code[offset + 1] = jump & 0xff;
|
||||
}
|
||||
int emitJump(OpCode instruction){
|
||||
compiler.emitter.emit(instruction, ubyte(0xff), ubyte(0xff));
|
||||
return cast(int)compiler.currentChunk.code.count - 2;
|
||||
currentCompiler.emit(instruction, ubyte(0xff), ubyte(0xff));
|
||||
return cast(int)currentCompiler.currentChunk.code.count - 2;
|
||||
}
|
||||
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)
|
||||
error("Loop body too large.");
|
||||
|
||||
compiler.emitter.emit(cast(ubyte)((offset >> 8) & 0xff));
|
||||
compiler.emitter.emit(cast(ubyte)(offset & 0xff));
|
||||
currentCompiler.emit(cast(ubyte)((offset >> 8) & 0xff));
|
||||
currentCompiler.emit(cast(ubyte)(offset & 0xff));
|
||||
}
|
||||
void defineVariable(long global){
|
||||
if(compiler.scopeDepth > 0){
|
||||
void defineVariable(int global){
|
||||
if(currentCompiler.scopeDepth > 0){
|
||||
markInitialised();
|
||||
return;
|
||||
}
|
||||
compiler.emitter.emit(OpCode.DefineGlobal, VarUint(global).bytes);
|
||||
currentCompiler.emit(OpCode.DefineGlobal, VarUint(global).bytes);
|
||||
}
|
||||
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(){
|
||||
if(compiler.scopeDepth == 0)
|
||||
if(currentCompiler.scopeDepth == 0)
|
||||
return;
|
||||
Token* name = &compiler.parser.previous;
|
||||
for(long i = compiler.locals.count - 1; i >= 0; i--){
|
||||
Compiler.Local* local = &compiler.locals[i];
|
||||
if(local.depth != Compiler.Local.Uninitialised && local.depth < compiler.scopeDepth)
|
||||
Token* name = &parser.previous;
|
||||
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
|
||||
currentCompiler.Local* local = ¤tCompiler.locals[i];
|
||||
if(local.depth != currentCompiler.Local.Uninitialised && local.depth < currentCompiler.scopeDepth)
|
||||
break;
|
||||
if(name.lexeme == local.name.lexeme)
|
||||
error("Already a variable with this name in this scope.");
|
||||
}
|
||||
addLocal(*name);
|
||||
}
|
||||
long parseVariable(const char* errorMessage){
|
||||
int parseVariable(const char* errorMessage){
|
||||
consume(Token.Type.Identifier, errorMessage);
|
||||
declareVariable();
|
||||
if(compiler.scopeDepth > 0)
|
||||
if(currentCompiler.scopeDepth > 0)
|
||||
return 0;
|
||||
return identifierConstant(&previous);
|
||||
}
|
||||
long identifierConstant(Token* name){
|
||||
int identifierConstant(Token* name){
|
||||
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
|
||||
return compiler.emitter.makeConstant(nameVal);
|
||||
return currentCompiler.makeConstant(nameVal);
|
||||
}
|
||||
void namedVariable(Token name, bool canAssign){
|
||||
OpCode getOp, setOp;
|
||||
long arg = resolveLocal(&name);
|
||||
int arg = resolveLocal(&name);
|
||||
if(arg != -1){
|
||||
getOp = OpCode.GetLocal;
|
||||
setOp = OpCode.SetLocal;
|
||||
|
|
@ -245,17 +287,28 @@ struct Parser{
|
|||
}
|
||||
if(canAssign && match(Token.Type.Equal)){
|
||||
expression();
|
||||
compiler.emitter.emit(setOp, VarUint(arg).bytes);
|
||||
currentCompiler.emit(setOp, VarUint(arg).bytes);
|
||||
} 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){
|
||||
for(long i = compiler.locals.count - 1; i >= 0; i--){
|
||||
Compiler.Local* local = &compiler.locals[i];
|
||||
int resolveLocal(Token* name){
|
||||
for(int i = cast(int)currentCompiler.locals.count - 1; i >= 0; i--){
|
||||
currentCompiler.Local* local = ¤tCompiler.locals[i];
|
||||
if(name.lexeme == local.name.lexeme){
|
||||
if(local.depth == Compiler.Local.Uninitialised)
|
||||
if(local.depth == currentCompiler.Local.Uninitialised)
|
||||
error("Can't read local variable in its own initialiser.");
|
||||
return i;
|
||||
}
|
||||
|
|
@ -263,16 +316,16 @@ struct Parser{
|
|||
return -1;
|
||||
}
|
||||
void addLocal(Token name){
|
||||
compiler.locals ~= Compiler.Local(name, Compiler.Local.Uninitialised);
|
||||
currentCompiler.locals ~= currentCompiler.Local(name, currentCompiler.Local.Uninitialised);
|
||||
}
|
||||
void beginScope(){
|
||||
compiler.scopeDepth++;
|
||||
currentCompiler.scopeDepth++;
|
||||
}
|
||||
void endScope(){
|
||||
assert(--compiler.scopeDepth >= 0);
|
||||
while(compiler.locals.count > 0 && compiler.locals[compiler.locals.count - 1].depth > compiler.scopeDepth){
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
compiler.locals.count--;
|
||||
assert(--currentCompiler.scopeDepth >= 0);
|
||||
while(currentCompiler.locals.count > 0 && currentCompiler.locals[currentCompiler.locals.count - 1].depth > currentCompiler.scopeDepth){
|
||||
currentCompiler.emit(OpCode.Pop);
|
||||
currentCompiler.locals.count--;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,11 +337,11 @@ struct Parser{
|
|||
return;
|
||||
}
|
||||
bool canAssign = precedence <= Precedence.Assignment;
|
||||
prefixRule(compiler, canAssign);
|
||||
prefixRule(currentCompiler, canAssign);
|
||||
while(precedence <= ParseRule.get(current.type).precedence){
|
||||
advance();
|
||||
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
||||
infixRule(compiler);
|
||||
infixRule(currentCompiler);
|
||||
}
|
||||
if(canAssign && match(Token.Type.Equal))
|
||||
error("Invalid assignment target.");
|
||||
|
|
|
|||
|
|
@ -4,43 +4,43 @@ import clox.chunk;
|
|||
import clox.compiler;
|
||||
import clox.parser;
|
||||
import clox.scanner;
|
||||
import clox.emitter;
|
||||
import clox.value;
|
||||
import clox.object;
|
||||
import clox.container.varint;
|
||||
|
||||
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);
|
||||
|
||||
private void grouping(Compiler* compiler, bool _){
|
||||
compiler.parser.expression();
|
||||
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
||||
parser.expression();
|
||||
parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
||||
}
|
||||
private void unary(Compiler* compiler, bool _){
|
||||
Token operator = compiler.parser.previous;
|
||||
compiler.parser.parsePrecedence(Precedence.Unary);
|
||||
Token operator = parser.previous;
|
||||
parser.parsePrecedence(Precedence.Unary);
|
||||
switch(operator.type){
|
||||
case Token.Type.Minus: compiler.emitter.emit(OpCode.Negate); break;
|
||||
case Token.Type.Bang: compiler.emitter.emit(OpCode.Not); break;
|
||||
case Token.Type.Minus: compiler.emit(OpCode.Negate); break;
|
||||
case Token.Type.Bang: compiler.emit(OpCode.Not); break;
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
private void binary(Compiler* compiler, bool _){
|
||||
Token operator = compiler.parser.previous;
|
||||
Token operator = parser.previous;
|
||||
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){
|
||||
case Token.Type.Plus: compiler.emitter.emit(OpCode.Add); break;
|
||||
case Token.Type.Minus: compiler.emitter.emit(OpCode.Subtract); break;
|
||||
case Token.Type.Star: compiler.emitter.emit(OpCode.Multiply); break;
|
||||
case Token.Type.Slash: compiler.emitter.emit(OpCode.Divide); break;
|
||||
case Token.Type.Plus: compiler.emit(OpCode.Add); break;
|
||||
case Token.Type.Minus: compiler.emit(OpCode.Subtract); break;
|
||||
case Token.Type.Star: compiler.emit(OpCode.Multiply); break;
|
||||
case Token.Type.Slash: compiler.emit(OpCode.Divide); break;
|
||||
|
||||
case Token.Type.BangEqual: compiler.emitter.emit(OpCode.NotEqual); break;
|
||||
case Token.Type.EqualEqual: compiler.emitter.emit(OpCode.Equal); break;
|
||||
case Token.Type.BangEqual: compiler.emit(OpCode.NotEqual); break;
|
||||
case Token.Type.EqualEqual: compiler.emit(OpCode.Equal); break;
|
||||
|
||||
case Token.Type.Greater: compiler.emitter.emit(OpCode.Greater); break;
|
||||
case Token.Type.GreaterEqual: compiler.emitter.emit(OpCode.GreaterEqual); break;
|
||||
case Token.Type.Greater: compiler.emit(OpCode.Greater); break;
|
||||
case Token.Type.GreaterEqual: compiler.emit(OpCode.GreaterEqual); break;
|
||||
|
||||
case Token.Type.Less: compiler.emitter.emit(OpCode.Less); break;
|
||||
case Token.Type.LessEqual: compiler.emitter.emit(OpCode.LessEqual); break;
|
||||
case Token.Type.Less: compiler.emit(OpCode.Less); break;
|
||||
case Token.Type.LessEqual: compiler.emit(OpCode.LessEqual); break;
|
||||
|
||||
default: assert(0);
|
||||
}
|
||||
|
|
@ -48,45 +48,49 @@ private void binary(Compiler* compiler, bool _){
|
|||
|
||||
private void number(Compiler* compiler, bool _){
|
||||
import core.stdc.stdlib : strtod;
|
||||
Token token = compiler.parser.previous;
|
||||
Token token = parser.previous;
|
||||
double value = strtod(token.lexeme.ptr, null);
|
||||
compiler.emitter.emitConstant(Value.from(value));
|
||||
compiler.emitConstant(Value.from(value));
|
||||
}
|
||||
private void literal(Compiler* compiler, bool _){
|
||||
Token token = compiler.parser.previous;
|
||||
Token token = parser.previous;
|
||||
switch(token.type){
|
||||
case Token.Type.True: compiler.emitter.emit(OpCode.True); break;
|
||||
case Token.Type.False: compiler.emitter.emit(OpCode.False); break;
|
||||
case Token.Type.Nil: compiler.emitter.emit(OpCode.Nil); break;
|
||||
case Token.Type.True: compiler.emit(OpCode.True); break;
|
||||
case Token.Type.False: compiler.emit(OpCode.False); break;
|
||||
case Token.Type.Nil: compiler.emit(OpCode.Nil); break;
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
private void strlit(Compiler* compiler, bool _){
|
||||
Token token = compiler.parser.previous;
|
||||
Token token = parser.previous;
|
||||
const char[] str = token.lexeme[1 .. $-1];
|
||||
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){
|
||||
compiler.parser.namedVariable(compiler.parser.previous, canAssign);
|
||||
parser.namedVariable(parser.previous, canAssign);
|
||||
}
|
||||
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.parser.parsePrecedence(Precedence.And);
|
||||
compiler.emit(OpCode.Pop);
|
||||
parser.parsePrecedence(Precedence.And);
|
||||
|
||||
compiler.parser.patchJump(endJump);
|
||||
parser.patchJump(endJump);
|
||||
}
|
||||
private void or(Compiler* compiler, bool _){
|
||||
int elseJump = compiler.parser.emitJump(OpCode.JumpIfFalse);
|
||||
int endJump = compiler.parser.emitJump(OpCode.Jump);
|
||||
int elseJump = parser.emitJump(OpCode.JumpIfFalse);
|
||||
int endJump = parser.emitJump(OpCode.Jump);
|
||||
|
||||
compiler.parser.patchJump(elseJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
parser.patchJump(elseJump);
|
||||
compiler.emit(OpCode.Pop);
|
||||
|
||||
compiler.parser.parsePrecedence(Precedence.Or);
|
||||
compiler.parser.patchJump(endJump);
|
||||
parser.parsePrecedence(Precedence.Or);
|
||||
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 = [
|
||||
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.LeftBrace : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ struct Token {
|
|||
int line;
|
||||
}
|
||||
|
||||
Scanner scanner;
|
||||
|
||||
bool isDigit(char c) => c >= '0' && c <= '9';
|
||||
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.stdlib;
|
||||
import core.stdc.string;
|
||||
import std.functional : ctEval;
|
||||
|
||||
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);
|
||||
|
||||
static char* readFile(const char* path) {
|
||||
FILE* file = path.fopen("rb");
|
||||
static char[] readFile(const(char)[] path){
|
||||
FILE* file = path.ptr.fopen("rb");
|
||||
if(file == null){
|
||||
stderr.fprintf("Could not open file \"%s\".\n", path);
|
||||
stderr.fprintf("Could not open file \"%s\".\n", path.ptr);
|
||||
return null;
|
||||
}
|
||||
scope(exit)
|
||||
|
|
@ -29,7 +30,7 @@ static char* readFile(const char* path) {
|
|||
size_t bytesRead = fread(buffer, char.sizeof, fileSize, file);
|
||||
buffer[bytesRead] = '\0';
|
||||
|
||||
return buffer;
|
||||
return buffer[0 .. bytesRead];
|
||||
}
|
||||
|
||||
char* readStdin(){
|
||||
|
|
@ -52,4 +53,6 @@ char* readStdin(){
|
|||
return buffer;
|
||||
}
|
||||
|
||||
const(char)[] parse(const char* cstr) => cstr[0 .. cstr.strlen];
|
||||
|
||||
|
||||
|
|
|
|||
191
src/clox/vm.d
191
src/clox/vm.d
|
|
@ -9,70 +9,87 @@ import clox.object;
|
|||
import clox.value;
|
||||
import clox.object;
|
||||
import clox.compiler;
|
||||
import clox.scanner;
|
||||
import clox.memory;
|
||||
import clox.container.stack;
|
||||
import clox.container.table;
|
||||
import clox.container.varint;
|
||||
|
||||
VM vm;
|
||||
|
||||
struct VM{
|
||||
Chunk* chunk;
|
||||
ubyte* ip;
|
||||
Stack!Value stack;
|
||||
Value[1024] stack;
|
||||
Value* stackTop;
|
||||
CallFrame[256] frames;
|
||||
uint frameCount;
|
||||
Table globals;
|
||||
Table strings;
|
||||
Obj* objects;
|
||||
bool isREPL;
|
||||
debug(printCode) bool printCode;
|
||||
debug(traceExec) bool traceExec;
|
||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||
struct CallFrame{
|
||||
Obj.Function* func;
|
||||
ubyte* ip;
|
||||
Value* slots;
|
||||
}
|
||||
void initialise(){
|
||||
stack.initialise();
|
||||
strings.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(){
|
||||
freeObjects();
|
||||
strings.free();
|
||||
globals.free();
|
||||
stack.free();
|
||||
}
|
||||
void push(Value v){
|
||||
*(stackTop++) = v;
|
||||
}
|
||||
Value pop(){
|
||||
return *(--stackTop);
|
||||
}
|
||||
InterpretResult interpret(const char* source){
|
||||
Chunk cnk;
|
||||
cnk.initialise();
|
||||
scope(exit)
|
||||
cnk.free();
|
||||
scanner.initialise(source);
|
||||
Compiler compiler;
|
||||
if(!compiler.compile(source, &cnk))
|
||||
compiler.initialise(Compiler.FunctionType.Script);
|
||||
Obj.Function* func = compiler.compile();
|
||||
if(func is null)
|
||||
return InterpretResult.CompileError;
|
||||
this.chunk = &cnk;
|
||||
this.ip = cnk.code.ptr;
|
||||
push(Value.from(func));
|
||||
call(func, 0);
|
||||
return run();
|
||||
}
|
||||
void runtimeError(Args...)(const char* format, Args args){
|
||||
fprintf(stderr, format, args);
|
||||
fputs("\n", stderr);
|
||||
|
||||
size_t instruction = ip - chunk.code.ptr - 1;
|
||||
uint line = chunk.lines[instruction];
|
||||
fprintf(stderr, "[line %d] in script\n", line);
|
||||
printStackTrace();
|
||||
}
|
||||
InterpretResult run(){
|
||||
debug(traceExec){
|
||||
printHeader(chunk);
|
||||
CallFrame* frame = &frames[frameCount - 1];
|
||||
ref ip() => frame.ip;
|
||||
ref chunk() => frame.func.chunk;
|
||||
debug(traceExec) if(traceExec){
|
||||
printHeader(&chunk());
|
||||
}
|
||||
ubyte readByte() => *ip++;
|
||||
ushort readShort(){
|
||||
ip += 2;
|
||||
return (ip[-2] << 8) | ip[-1];
|
||||
}
|
||||
long readVarUint(){
|
||||
int readVarUint(){
|
||||
VarUint vi = VarUint.read(ip);
|
||||
ip += vi.len;
|
||||
return vi.i;
|
||||
}
|
||||
Value readConstant() => chunk.constants[readVarUint()];
|
||||
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 checkSameType()() => peek(0).type == peek(1).type;
|
||||
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||
|
|
@ -80,32 +97,34 @@ struct VM{
|
|||
runtimeError(checkMsg.ptr);
|
||||
return 1;
|
||||
}
|
||||
auto b = stack.pop().as!pre;
|
||||
auto a = stack.pop().as!pre;
|
||||
stack ~= Value.from(mixin("a", op, "b"));
|
||||
auto b = pop().as!pre;
|
||||
auto a = pop().as!pre;
|
||||
push(Value.from(mixin("a", op, "b")));
|
||||
return 0;
|
||||
}
|
||||
while(true){
|
||||
debug(traceExec){
|
||||
disassembleInstruction(chunk, ip - chunk.code.ptr);
|
||||
printf(" ");
|
||||
foreach(slot; stack.live){
|
||||
printf("");
|
||||
slot.print();
|
||||
printf(colour!(", ", Colour.Black).ptr);
|
||||
debug(traceExec) if(traceExec){
|
||||
disassembleInstruction(&chunk(), ip - chunk.code.ptr);
|
||||
printf(colour!(" [", Colour.Black).ptr);
|
||||
for(Value* v = stack.ptr+1; v < stackTop; v++){
|
||||
printf(" ");
|
||||
v.print();
|
||||
if(v+1 < stackTop)
|
||||
printf(colour!(",", Colour.Black).ptr);
|
||||
printf(" ");
|
||||
}
|
||||
printf("\n");
|
||||
printf(colour!("]\n", Colour.Black).ptr);
|
||||
}
|
||||
OpCode instruction;
|
||||
opSwitch: final switch(instruction = cast(OpCode)readByte()){
|
||||
case OpCode.Constant:
|
||||
Value constant = readConstant();
|
||||
stack ~= constant;
|
||||
push(constant);
|
||||
break;
|
||||
case OpCode.Nil: stack ~= Value.nil; break;
|
||||
case OpCode.True: stack ~= Value.from(true); break;
|
||||
case OpCode.False: stack ~= Value.from(false); break;
|
||||
case OpCode.Pop: stack.pop(); break;
|
||||
case OpCode.Nil: push(Value.nil); break;
|
||||
case OpCode.True: push(Value.from(true)); break;
|
||||
case OpCode.False: push(Value.from(false)); break;
|
||||
case OpCode.Pop: pop(); break;
|
||||
|
||||
case OpCode.Jump:
|
||||
ushort offset = readShort();
|
||||
|
|
@ -123,11 +142,11 @@ struct VM{
|
|||
|
||||
case OpCode.GetLocal:
|
||||
long slot = readVarUint();
|
||||
stack ~= stack[slot];
|
||||
push(frame.slots[slot]);
|
||||
break;
|
||||
case OpCode.SetLocal:
|
||||
long slot = readVarUint();
|
||||
stack[slot] = peek(0);
|
||||
frame.slots[slot] = peek(0);
|
||||
break;
|
||||
case OpCode.GetGlobal:
|
||||
Obj.String* name = readString();
|
||||
|
|
@ -136,12 +155,12 @@ struct VM{
|
|||
runtimeError("Undefined variable '%s'.", name.chars.ptr);
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
stack ~= value;
|
||||
push(value);
|
||||
break;
|
||||
case OpCode.DefineGlobal:
|
||||
Obj.String* name = readString();
|
||||
globals.set(name, peek(0));
|
||||
stack.pop();
|
||||
pop();
|
||||
break;
|
||||
case OpCode.SetGlobal:
|
||||
Obj.String* name = readString();
|
||||
|
|
@ -164,14 +183,14 @@ struct VM{
|
|||
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:
|
||||
static if(k == OpCode.Add){
|
||||
if(checkBinaryType!(Obj.Type.String)){
|
||||
Obj.String* b = stack.pop().asObj.asString;
|
||||
Obj.String* a = stack.pop().asObj.asString;
|
||||
Obj.String* b = pop().asObj.asString;
|
||||
Obj.String* a = pop().asObj.asString;
|
||||
Obj.String* result = Obj.String.concat(a, b);
|
||||
stack ~= Value.from(result);
|
||||
push(Value.from(result));
|
||||
break opSwitch;
|
||||
}
|
||||
}
|
||||
|
|
@ -181,24 +200,98 @@ struct VM{
|
|||
}
|
||||
|
||||
case OpCode.Not:
|
||||
stack ~= Value.from(stack.pop().isFalsey);
|
||||
push(Value.from(pop().isFalsey));
|
||||
break;
|
||||
case OpCode.Negate:
|
||||
if(!peek(0).isType(Value.Type.Number)){
|
||||
runtimeError("Operand must be a number.");
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
stack ~= Value.from(-stack.pop().asNumber);
|
||||
push(Value.from(-pop().asNumber));
|
||||
break;
|
||||
case OpCode.Print:
|
||||
stack.pop().print("%s");
|
||||
pop().print("%s");
|
||||
printf("\n");
|
||||
break;
|
||||
|
||||
case OpCode.Call:
|
||||
int argCount = readVarUint();
|
||||
if(!callValue(peek(argCount), argCount))
|
||||
return InterpretResult.RuntimeError;
|
||||
frame = &frames[frameCount - 1];
|
||||
break;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n");
|
||||
"./test/closure.lox".match("1\n2\n");
|
||||
"./test/scope.lox".match("global first first second first ".replace(' ', '\n').repeat(2).join("\n"));
|
||||
/* "./test/closure.lox".match("1\n2\n"); */
|
||||
"./test/scope.lox".match("global first first second first ".replace(' ', '\n'));
|
||||
"./test/fib_for.lox".match(fib(6765));
|
||||
"./test/fib_recursive.lox".match(fib(34));
|
||||
"./test/fib_closure.lox".match(fib(34));
|
||||
"./test/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/fib_closure.lox".match(fib(34)); */
|
||||
/* "./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/err/invalid_syntax.lox".shouldFail(RetVal.other);
|
||||
"./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/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/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_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"); */
|
||||
}
|
||||
|
||||
enum RetVal{
|
||||
success = 0, other = 1, runtime = 2
|
||||
success = 0, other = 65, runtime = 70
|
||||
}
|
||||
|
||||
string fib(uint n){
|
||||
|
|
@ -42,13 +51,13 @@ string fib(uint n){
|
|||
}
|
||||
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){
|
||||
auto res = file.run.output;
|
||||
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){
|
||||
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(!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));
|
||||
|
|
|
|||
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();
|
||||
print "";
|
||||
f();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue