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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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