Compare commits

..

No commits in common. "10c44eab2ecc3a492fe0de24d47eb584295cd99c" and "a21c16d7e5c475fd814781067eea7a0ed4f724ea" have entirely different histories.

48 changed files with 198 additions and 1889 deletions

1
.gitignore vendored
View file

@ -16,5 +16,4 @@ lox-test-*
*.lst *.lst
.msc/ .msc/
test/test.lox

18
dub.sdl
View file

@ -4,19 +4,13 @@ authors "tanya"
copyright "Copyright © 2025, tanya" copyright "Copyright © 2025, tanya"
license "MPL-2.0" license "MPL-2.0"
dependency "commandr" version="~>1.1.0" dependency "commandr" version="~>1.1.0"
dependency "colored" version="~>0.0.33" dependency "taggedalgebraic" version="~>0.11.23"
targetType "executable" targetType "executable"
sourcePaths buildRequirements "requireBoundsCheck" "requireContracts"
configuration "clox" {
/* debugVersions "traceExec" */ versions "LoxConcatNonStrings" "LoxExtraNativeFuncs"
debugVersions "printCode"
targetType "executable"
sourcePaths "src/clox" "src/common"
buildRequirements "requireBoundsCheck" "requireContracts"
}
configuration "jlox" { configuration "jlox" {
targetType "executable"
sourcePaths "src/jlox" "src/common" sourcePaths "src/jlox" "src/common"
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
buildRequirements "requireBoundsCheck" "requireContracts"
} }

View file

@ -1,9 +1,7 @@
{ {
"fileVersion": 1, "fileVersion": 1,
"versions": { "versions": {
"colored": "0.0.33",
"commandr": "1.1.0", "commandr": "1.1.0",
"taggedalgebraic": "0.11.23", "taggedalgebraic": "0.11.23"
"unit-threaded": "2.2.3"
} }
} }

View file

@ -1,69 +0,0 @@
module clox.chunk;
import std.container.array;
import std.stdio;
import std.algorithm.searching;
import clox.value;
import clox.container.rle;
import clox.container.int24;
enum SimpleOp;
enum LogicOp;
enum ValueOp;
enum ArithOp;
enum CompOp;
enum OpCode : ubyte{
Constant,
@ValueOp Nil,
@ValueOp True,
@ValueOp False,
@(SimpleOp, CompOp) Equal,
@(SimpleOp, CompOp) Greater,
@(SimpleOp, CompOp) Less,
@(SimpleOp, CompOp) NotEqual,
@(SimpleOp, CompOp) GreaterEqual,
@(SimpleOp, CompOp) LessEqual,
@(SimpleOp, ArithOp) Add,
@(SimpleOp, ArithOp) Subtract,
@(SimpleOp, ArithOp) Multiply,
@(SimpleOp, ArithOp) Divide,
@(SimpleOp, LogicOp) Not,
@(SimpleOp, LogicOp) Negate,
@(SimpleOp) Return,
}
import std.traits: hasUDA;
bool isSimpleOp(alias op)() => hasUDA!(op, SimpleOp);
bool isValueOp(alias op)() => hasUDA!(op, ValueOp);
bool isLogicOp(alias op)() => hasUDA!(op, LogicOp);
bool isCompOp(alias op)() => hasUDA!(op, CompOp);
bool isArithOp(alias op)() => hasUDA!(op, ArithOp);
bool isSize1Op(alias op)() => isSimpleOp!op || isValueOp!op;
static assert( isSimpleOp!(OpCode.Equal) );
static assert( !isSimpleOp!(OpCode.Constant) );
struct Chunk{
Array!ubyte code;
Rle!(Uint24, ubyte) lines;
Array!Value constants;
uint addConstant(in Value value) @nogc nothrow {
long index = constants[].countUntil(value);
if(index >= 0)
return cast(uint)index;
constants ~= value;
return cast(uint)((constants.length) - 1);
}
void write(ubyte b, uint line = 0) @nogc nothrow {
ubyte[1] data = [ b ];
write(data, line);
}
void write(ubyte[] b, uint line = 0) @nogc nothrow {
code ~= b;
foreach(i; 0 .. b.length)
lines ~= Uint24(line); // TODO could be done without a loop
}
}

View file

@ -1,30 +0,0 @@
module clox.compiler;
import std.stdio;
import clox.scanner, clox.parser, clox.emitter;
import clox.chunk;
import clox.value;
import clox.util;
import clox.parserules;
import clox.dbg;
struct Compiler{
Scanner scanner;
Parser parser;
Emitter emitter;
bool compile(string source, Chunk* chunk){
scanner = Scanner(source);
parser = Parser(&this);
emitter = Emitter(&this, chunk);
parser.advance();
parser.expression();
parser.consume(Token.Type.EOF, "Expect end of expression.");
debug writeln(*chunk);
emitter.endCompiler();
return !parser.hadError;
}
}

View file

@ -1,26 +0,0 @@
module clox.container.int24;
struct Uint24{
nothrow: @nogc: @safe:
ubyte[3] data;
static Uint24 opCall(uint n){
import std.bitmanip : nativeToLittleEndian;
Uint24 u3;
assert(n <= 16_777_215);
ubyte[uint.sizeof] d = nativeToLittleEndian!uint(n);
u3.data[0 .. 3] = d[0 .. 3];
return u3;
}
}
uint toUint(Uint24 u3) @nogc nothrow @safe {
import std.bitmanip : littleEndianToNative;
ubyte[4] temp;
temp[0 .. 3] = u3.data;
return littleEndianToNative!uint(temp);
}
unittest{
static assert(Uint24.sizeof == 3);
assert(Uint24(5).toUint == 5);
assert(Uint24(16_777_215).toUint == 16_777_215);
}

View file

@ -1,62 +0,0 @@
module clox.container.rle;
import std.traits;
import std.container.array;
struct Rle(T, L = ubyte) if(isUnsigned!L){
nothrow: @nogc:
align(1) struct Count{
T item;
L num;
}
size_t total;
Array!Count data;
private void pushNew(T item){
data ~= Count(item, 0);
}
size_t push(T item){
if(data.length){
Count* d = &data[(data.length)-1];
if(d.item == item && d.num < L.max){
d.num++;
return total++;
}
}
pushNew(item);
return total++;
}
T opOpAssign(string op: "~")(T rhs){
push(rhs);
return rhs;
}
T opIndex(size_t n) const @safe{
assert(n < total);
size_t c;
for(size_t i; i < n;){
i += data[c].num + 1;
if(i <= n)
c++;
}
return data[c].item;
}
}
unittest{
import clox.container.int24;
auto rl = Rle!(Uint24, ubyte)();
static assert(rl.Count.sizeof == 4);
foreach(i; 0..300){
size_t index = rl.push(Uint24(5));
assert(rl[index].toUint == 5);
}
assert(rl[299].toUint == 5);
foreach(i; 0..30){
size_t index = rl.push(Uint24(0));
assert(rl[index].toUint == 0);
}
assert(rl[0].toUint == 5);
foreach(i; 0..300){
size_t index = rl.push(Uint24(16_777_215));
assert(rl[index].toUint == 16_777_215);
}
}

View file

@ -1,26 +0,0 @@
module clox.container.stack;
struct Stack(T, size_t N){
@nogc: nothrow:
T* top;
T[N] data;
invariant{ assert(top <= data.ptr + N); assert(top >= data.ptr); }
this(int _) @safe{
top = data.ptr;
}
void push(T value){
assert(top < data.ptr + N);
debug assert(*top is T.init);
*(top++) = value;
}
T pop(){
assert(top > data.ptr);
T t = *(--top);
debug *(top) = T.init;
return t;
}
const(T)[] live() const @safe{
return data[0 .. (top - data.ptr)];
}
}

View file

@ -1,73 +0,0 @@
module clox.container.varint;
struct VarUint{
import std.bitmanip;
nothrow: @nogc:
uint i;
ubyte len;
ubyte[4] data;
this(long l) @safe {
if(l < 0b1000_0000){
len = 1;
data[0] = (cast(ubyte)l);
return;
}
if(l < 0b0100_0000__0000_0000){
len = 2;
data[0 .. 2] = nativeToBigEndian(cast(ushort)l);
data[0] |= 0b1000_0000;
return;
}
if(l < 0b0010_0000__0000_0000__0000_0000__0000_0000){
len = 4;
data[0 .. 4] = nativeToBigEndian(cast(uint)l);
data[0] |= 0b1100_0000;
return;
}
assert(0);
}
static VarUint read(const(ubyte)[] data) @safe {
VarUint v;
ubyte a = data[0];
if((data[0] & 0b1000_0000) == 0){
v.i = a;
v.len = 1;
return v;
}
if((a & 0b0100_0000) == 0){
ubyte[2] d = data[0 .. 2];
d[0] &= 0b0111_1111;
v.i = bigEndianToNative!ushort(d);
v.len = 2;
return v;
}
if((a & 0b0010_0000) == 0){
ubyte[4] d = data[0 .. 4];
d[0] &= 0b0011_1111;
v.i = bigEndianToNative!uint(d);
v.len = 4;
return v;
}
assert(0);
}
ubyte[] bytes() @nogc nothrow {
return data[0 .. len];
}
}
unittest{
import std.range;
assert(VarUint(5).bytes.length == 1);
assert(VarUint(127).bytes.length == 1);
assert(VarUint(128).bytes.length == 2);
assert(VarUint(536_870_911).bytes.length == 4);
foreach(ulong i; [
0, 1, 2, 5,
150, 127, 128,
536_870_911,
ushort.max * 100
]){
auto vi = VarUint(i);
assert(i == VarUint.read(vi.bytes).i);
}
}

View file

@ -1,59 +0,0 @@
module clox.container.vartype;
import std.stdio;
import std.algorithm;
import std.array;
import std.uni;
import std.conv;
struct VarType(S) if(is(S == union)){
private enum members = __traits(derivedMembers, S);
mixin("enum Type{ None, ", [members].map!asCapitalized.join(", "), "}");
private S value;
private Type _type;
Type type() const @safe @nogc nothrow => _type;
private void check(Type t) const @safe nothrow @nogc{
assert(this.type == t, "Tried to get wrong type");
}
private template funcs(string G, string T){
mixin("bool is", G, "() const nothrow @nogc @safe => this.type == this.Type.", G, ";");
mixin("auto get", G, "() const nothrow @nogc { check(this.Type.", G, "); return value.", T, "; }");
mixin("void set", G, "(typeof(S.", T, ") v = typeof(S.", T, ").init){ this._type = this.Type.", G, "; this.value.", T, " = v; }");
mixin("static auto ", T, "(typeof(S.", T, ") v){ typeof(this) vt; vt.set", G, "(v); return vt; }");
mixin("static auto ", T, "(){ typeof(this) vt; vt.set", G, "(); return vt; }");
}
static foreach(s; members){
mixin funcs!(s.asCapitalized.to!string, s);
}
string toString() const{
final switch(_type){
static foreach(s; members){
mixin("case Type.", s.asCapitalized.to!string, ": return _type.to!string ~ ':' ~ get", s.asCapitalized.to!string, ".to!string ;");
}
case Type.None: return "None";
}
}
}
unittest{
import std.exception, std.stdio;
union Test{
uint u;
int i;
double d;
}
auto i = VarType!Test.i(-5);
assert(i.getI == -5);
assert(i.type == i.Type.I);
assert(i.isI);
assert(!i.isD);
i.setD(0.5);
assert(i.getD == 0.5);
assert(i.type == i.Type.D);
assert(i.isD);
assert(!i.isU);
auto i2 = VarType!Test.i();
assert(i2.isI);
}

View file

@ -1,66 +0,0 @@
module clox.dbg;
import std.stdio;
import std.conv;
import std.uni;
import std.format;
import colored;
import clox.chunk;
import clox.value;
import clox.util;
import clox.container.varint;
import clox.container.int24;
private ulong simpleInstruction(alias op)(string name, ulong offset){
static if(isValueOp!op)
writeln(name.cyan);
else static if(isLogicOp!op)
writeln(name.lightRed);
else static if(isCompOp!op)
writeln(name.red);
else static if(isArithOp!op)
writeln(name.yellow);
else
writeln(name.lightCyan);
return offset + 1;
}
private ulong constantInstruction(string name, Chunk* chunk, ulong offset){
/* ubyte constant = chunk.code[offset + 1]; */
VarUint constant = VarUint.read(chunk.code.data[offset + 1 .. $]);
/* writeln(constant); */
write("%-16s".format(name).cyan, " %4d ".format(constant.i).lightMagenta, "'");
printValue(chunk.constants[constant.i]);
writeln("'");
return offset + 1 + constant.len;
}
void disassembleChunk(Chunk* chunk, string name = "chunk"){
writefln("== %s ==", name);
for(ulong offset = 0; offset < chunk.code.length;)
offset = disassembleInstruction(chunk, offset);
}
ulong disassembleInstruction(Chunk* chunk, const ulong offset){
write(" %04d ".format(offset).lightGray);
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
write(" | ".darkGray);
} else {
write(" %4d ".format(chunk.lines[offset].toUint).lightGray);
}
ubyte instruction = chunk.code[offset];
with(OpCode) switch(instruction){
import std.meta, std.traits;
case Constant:
return constantInstruction("OP_CONSTANT", chunk, offset);
static foreach(k; Filter!(isSize1Op, EnumMembers!OpCode)){
case k:
static name = "OP_" ~ (k.to!string).toUpper;
return simpleInstruction!k(name, offset);
}
default:
writefln("Unknown opcode %d", instruction);
return offset + 1;
}
}

View file

@ -1,51 +0,0 @@
module clox.emitter;
import clox.compiler;
import clox.chunk;
import clox.value;
import clox.util;
import clox.dbg;
import clox.container.varint;
struct Emitter{
Compiler* compiler;
Chunk* chunk;
private uint line = 1;
Chunk* currentChunk(){
return chunk;
}
void emit(Args...)(Args args){
static foreach(v; args){{
static if(is(typeof(v) == OpCode)){
auto bytes = v;
} else static if(is(typeof(v) == uint)){
auto bytes = VarUint(v).bytes;
} else {
static assert(0);
}
currentChunk.write(bytes, line);
}}
}
void emitConstant(Value value){
emit(OpCode.Constant, makeConstant(value));
}
void emitReturn(){
emit(OpCode.Return);
}
void endCompiler(){
emitReturn();
debug(printCode){
if(!compiler.parser.hadError)
disassembleChunk(currentChunk());
}
}
uint makeConstant(Value value){
uint constant = chunk.addConstant(value);
return constant;
}
void setLine(uint l){
this.line = l;
}
}

View file

@ -1,46 +0,0 @@
module clox.main;
import std.stdio;
import std.file;
import clox.chunk;
import clox.dbg;
import clox.vm;
extern(C) int isatty(int);
struct Lox{
VM vm;
this(int _){
vm = VM(0);
}
int runFile(string path){
string source = path.readText();
VM.InterpretResult result = vm.interpret(source);
final switch(result){
case VM.InterpretResult.CompileError: return 65;
case VM.InterpretResult.RuntimeError: return 70;
case VM.InterpretResult.Ok: return 0;
}
}
int runPrompt(){
while(true){
write("lox> ");
string line = stdin.readln();
if(!line){
writeln();
return 0;
}
vm.interpret(line);
}
}
}
int main(string[] argv){
Lox lox = Lox(0);
if(isatty(stdin.fileno))
return lox.runPrompt();
else
return lox.runFile("/dev/stdin");
}

View file

@ -1,66 +0,0 @@
module clox.parser;
import clox.compiler;
import clox.value;
import clox.scanner;
import clox.parserules;
struct Parser{
Compiler* compiler;
Token current, previous;
bool hadError, panicMode;
void errorAtCurrent(string message){
errorAt(current, message);
}
void error(string message){
errorAt(previous, message);
}
void errorAt(in ref Token token, string message){
import core.stdc.stdio;
if(panicMode)
return;
panicMode = true;
fprintf(stderr, "[line %d] Error", token.line);
if(token.type == Token.Type.EOF){
fprintf(stderr, " at end");
} else if(token.type != Token.Type.Error){
fprintf(stderr, " at '%.*s'", cast(int)token.lexeme.length, token.lexeme.ptr);
}
fprintf(stderr, ": %.*s\n", cast(int)message.length, message.ptr);
hadError = true;
}
auto consume(Token.Type type, string msg){
if(current.type == type){
advance();
return;
}
errorAtCurrent(msg);
}
void advance(){
previous = current;
while(true){
current = compiler.scanner.scan();
if(current.type != Token.Type.Error)
break;
errorAtCurrent(current.lexeme);
}
}
void expression(){
parsePrecedence(Precedence.Assignment);
}
void parsePrecedence(Precedence precedence){
advance();
ParseFn prefixRule = ParseRule.get(previous.type).prefix;
if(prefixRule == null){
error("Expect expression.");
return;
}
prefixRule(compiler);
while(precedence <= ParseRule.get(current.type).precedence){
advance();
ParseFn infixRule = ParseRule.get(previous.type).infix;
infixRule(compiler);
}
}
}

View file

@ -1,128 +0,0 @@
module clox.parserules;
import clox.compiler;
import clox.chunk;
import clox.scanner;
import clox.value;
alias ParseFn = void function(Compiler* compiler);
private void number(Compiler* compiler){
import core.stdc.stdlib : strtod;
Token token = compiler.parser.previous;
double value = strtod(token.lexeme.ptr, null);
compiler.emitter.setLine(token.line);
compiler.emitter.emitConstant(Value.num(value));
}
private void grouping(Compiler* compiler){
compiler.parser.expression();
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
}
private void unary(Compiler* compiler){
Token operator = compiler.parser.previous;
compiler.parser.parsePrecedence(Precedence.Unary);
compiler.emitter.setLine(operator.line);
switch(operator.type){
case Token.Type.Minus: compiler.emitter.emit(OpCode.Negate); break;
case Token.Type.Bang: compiler.emitter.emit(OpCode.Not); break;
default: assert(0);
}
}
private void binary(Compiler* compiler){
Token operator = compiler.parser.previous;
immutable(ParseRule)* rule = ParseRule.get(operator.type);
compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1));
compiler.emitter.setLine(operator.line);
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.BangEqual: compiler.emitter.emit(OpCode.NotEqual); break;
case Token.Type.EqualEqual: compiler.emitter.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.Less: compiler.emitter.emit(OpCode.Less); break;
case Token.Type.LessEqual: compiler.emitter.emit(OpCode.LessEqual); break;
default: assert(0);
}
}
private void literal(Compiler* compiler){
switch(compiler.parser.previous.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;
default: assert(0);
}
}
struct ParseRule{
ParseFn prefix;
ParseFn infix;
Precedence precedence;
static immutable(ParseRule)* get(Token.Type type) @nogc nothrow{
return &rules[type];
}
}
enum Precedence{
None,
Assignment, // =
Or, // or
And, // and
Equality, // == !=
Comparison, // < > <= >=
Term, // + -
Factor, // * /
Unary, // ! -
Call, // . ()
Primary
}
immutable ParseRule[Token.Type.max+1] rules = [
Token.Type.LeftParen : ParseRule(&grouping, null, Precedence.None),
Token.Type.RightParen : ParseRule(null, null, Precedence.None),
Token.Type.LeftBrace : ParseRule(null, null, Precedence.None),
Token.Type.RightBrace : ParseRule(null, null, Precedence.None),
Token.Type.Comma : ParseRule(null, null, Precedence.None),
Token.Type.Dot : ParseRule(null, null, Precedence.None),
Token.Type.Minus : ParseRule(&unary, &binary, Precedence.Term),
Token.Type.Plus : ParseRule(null, &binary, Precedence.Term),
Token.Type.Semicolon : ParseRule(null, null, Precedence.None),
Token.Type.Slash : ParseRule(null, &binary, Precedence.Factor),
Token.Type.Star : ParseRule(null, &binary, Precedence.Factor),
Token.Type.Bang : ParseRule(&unary, null, Precedence.None),
Token.Type.BangEqual : ParseRule(null, &binary, Precedence.Equality),
Token.Type.Equal : ParseRule(null, null, Precedence.None),
Token.Type.EqualEqual : ParseRule(null, &binary, Precedence.Equality),
Token.Type.Greater : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.GreaterEqual : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.Less : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.LessEqual : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.Identifier : ParseRule(null, null, Precedence.None),
Token.Type.String : ParseRule(null, null, Precedence.None),
Token.Type.Number : ParseRule(&number, null, Precedence.None),
Token.Type.And : ParseRule(null, null, Precedence.None),
Token.Type.Class : ParseRule(null, null, Precedence.None),
Token.Type.Else : ParseRule(null, null, Precedence.None),
Token.Type.False : ParseRule(&literal, null, Precedence.None),
Token.Type.For : ParseRule(null, null, Precedence.None),
Token.Type.Fun : ParseRule(null, null, Precedence.None),
Token.Type.If : ParseRule(null, null, Precedence.None),
Token.Type.Nil : ParseRule(&literal, null, Precedence.None),
Token.Type.Or : ParseRule(null, null, Precedence.None),
Token.Type.Print : ParseRule(null, null, Precedence.None),
Token.Type.Return : ParseRule(null, null, Precedence.None),
Token.Type.Super : ParseRule(null, null, Precedence.None),
Token.Type.This : ParseRule(null, null, Precedence.None),
Token.Type.True : ParseRule(&literal, null, Precedence.None),
Token.Type.Var : ParseRule(null, null, Precedence.None),
Token.Type.While : ParseRule(null, null, Precedence.None),
Token.Type.Error : ParseRule(null, null, Precedence.None),
Token.Type.EOF : ParseRule(null, null, Precedence.None),
];

View file

@ -1,157 +0,0 @@
module clox.scanner;
import std.stdio;
import std.ascii;
import common.util;
struct Token{
enum Type : ubyte {
None, Error, EOF, // Special
LeftParen, RightParen, // Single-character tokens.
LeftBrace, RightBrace,
Comma, Dot, Minus, Plus,
Semicolon, Slash, Star,
Bang, BangEqual, // One or two character tokens.
Equal, EqualEqual,
Greater, GreaterEqual,
Less, LessEqual,
Identifier, String, Number, // Literals.
And, Class, Else, False, // Keywords.
For, Fun, If, Nil, Or,
Print, Return, Super, This,
True, Var, While,
}
Type type;
int line;
string lexeme;
static Token error(string msg) nothrow @nogc => Token(Token.Type.Error, 0, msg);
}
struct Scanner{
nothrow:
@nogc:
string start;
string current;
int line = 1;
this(string source) @nogc nothrow{
start = current = source;
}
bool isAtEnd() const => current.length == 0;
private char peek() const => current[0];
private char peekNext() const => current.length >= 2 ? current[1] : '\0';
private Token makeToken(Token.Type type) const{
Token token;
token.type = type;
token.lexeme = start[0 .. current.ptr - start.ptr];
token.line = line;
return token;
}
private char advance(){
char c = current[0];
current = current[1 .. $];
return c;
}
private bool match(char needle){
if(isAtEnd || current[0] != needle)
return false;
current = current[1 .. $];
return true;
}
private void skipWhitespace(){
while(!isAtEnd){
char c = peek();
if(!c)
return;
if(c == '/' && peekNext() == '/'){
while(!isAtEnd && peek() != '\n')
advance();
continue;
}
if(!c.isWhite)
return;
/* debug writeln(c == '\n'); */
if(c == '\n')
line++;
current = current[1 .. $];
}
}
private Token parseString(){
while(peek() != '"' && !isAtEnd){
if(peek() == '\n')
line++;
advance();
}
if(isAtEnd)
return Token.error("Unterminated string.");
advance();
return makeToken(Token.Type.String);
}
private Token parseNumber(){
while(peek().isDigit)
advance();
if(peek() == '.' && peekNext().isDigit){
advance();
while(peek().isDigit)
advance();
}
return makeToken(Token.Type.Number);
}
private Token parseIdentifier(){
while(peek().isAlphaNum_)
advance();
Token token = makeToken(Token.Type.Identifier);
switch(token.lexeme){
case "and": token.type = Token.Type.And; break;
case "class": token.type = Token.Type.Class; break;
case "else": token.type = Token.Type.Else; break;
case "if": token.type = Token.Type.If; break;
case "nil": token.type = Token.Type.Nil; break;
case "or": token.type = Token.Type.Or; break;
case "print": token.type = Token.Type.Print; break;
case "return": token.type = Token.Type.Return; break;
case "super": token.type = Token.Type.Super; break;
case "var": token.type = Token.Type.Var; break;
case "while": token.type = Token.Type.While; break;
case "false": token.type = Token.Type.False; break;
case "for": token.type = Token.Type.For; break;
case "fun": token.type = Token.Type.Fun; break;
case "this": token.type = Token.Type.This; break;
case "true": token.type = Token.Type.True; break;
default: break;
}
return token;
}
Token scan(){
skipWhitespace();
start = current;
if(isAtEnd)
return Token(Token.Type.EOF);
char c = advance();
if(c.isAlpha_)
return parseIdentifier();
switch(c){
case '(': return makeToken(Token.Type.LeftParen);
case ')': return makeToken(Token.Type.RightParen);
case '{': return makeToken(Token.Type.LeftBrace);
case '}': return makeToken(Token.Type.RightBrace);
case ';': return makeToken(Token.Type.Semicolon);
case ',': return makeToken(Token.Type.Comma);
case '.': return makeToken(Token.Type.Dot);
case '-': return makeToken(Token.Type.Minus);
case '+': return makeToken(Token.Type.Plus);
case '/': return makeToken(Token.Type.Slash);
case '*': return makeToken(Token.Type.Star);
case '!': return makeToken(match('=') ? Token.Type.BangEqual : Token.Type.Bang);
case '=': return makeToken(match('=') ? Token.Type.EqualEqual : Token.Type.Equal);
case '<': return makeToken(match('=') ? Token.Type.LessEqual : Token.Type.Less);
case '>': return makeToken(match('=') ? Token.Type.GreaterEqual : Token.Type.Greater);
case '"': return parseString();
default: break;
}
if(c.isDigit)
return parseNumber();
return Token.error("Unexpected character.");
}
}

View file

@ -1,6 +0,0 @@
module clox.util;
import std.stdio;
import std.traits : isUnsigned;
import std.container.array;

View file

@ -1,53 +0,0 @@
module clox.value;
import std.stdio;
import clox.container.vartype;
/* struct Value{ */
/* alias T = VarType!u; */
/* private union U{ */
/* bool bln; */
/* bool nil; */
/* double num; */
/* } */
/* T v; */
/* alias v this; */
/* } */
private union U{
bool bln;
bool nil;
double num;
}
alias Value = VarType!U;
void printValue(Value value){
final switch(value.type){
case value.Type.Bln: writef("%s", value.getBln); break;
case value.Type.Num: writef("%g", value.getNum); break;
case value.Type.Nil: writef("nil"); break;
case value.Type.None: assert(0);
}
}
bool isTruthy(Value value) nothrow @nogc {
final switch(value.type){
case value.Type.Bln: return value.getBln;
case value.Type.Num: return true;
case value.Type.Nil: return false;
case value.Type.None: assert(0);
}
}
bool isFalsey(Value value) nothrow @nogc {
return !isTruthy(value);
}
bool compare(string op)(Value a, Value b){
if(a.type != b.type)
return false;
final switch(a.type){
case a.Type.Bln: return mixin("a.getBln", op, "b.getBln");
case a.Type.Num: return mixin("a.getNum", op, "b.getNum");
case a.Type.Nil: return true;
case a.Type.None: assert(0);
}
}

View file

@ -1,114 +0,0 @@
module clox.vm;
import std.stdio;
import clox.chunk;
import clox.value;
import clox.dbg;
import clox.util;
import clox.compiler;
import clox.container.stack;
import clox.container.varint;
import clox.container.int24;
enum stackMax = 256;
struct VM{
const(ubyte)* ip;
Stack!(Value, stackMax) stack;
Chunk* chunk;
enum InterpretResult{ Ok, CompileError, RuntimeError }
this(int _) @nogc nothrow {
stack = typeof(stack)(0);
}
InterpretResult interpret(string source){
Chunk c = Chunk();
Compiler compiler;
if(!compiler.compile(source, &c))
return InterpretResult.CompileError;
chunk = &c;
return interpret(chunk);
}
InterpretResult interpret(Chunk* chunk){
this.chunk = chunk;
ip = &chunk.code[0];
return run();
}
private void runtimeError(Args...)(string format, Args args) nothrow {
size_t instruction = ip - (&chunk.code[0]) - 1;
uint line = chunk.lines[instruction].toUint;
try{
stderr.writef("[line %d] ", line);
stderr.writefln(format, args);
} catch(Exception){}
/* stack.reset(); */
}
private InterpretResult run() nothrow {
auto readByte() => *ip++;
auto readIns() => cast(OpCode)readByte();
Value readConstant(){
VarUint constant = VarUint.read(ip[0 .. 4]);
ip += constant.len;
return chunk.constants[constant.i];
}
Value peek(int distance = 0){
return stack.top[-1 - distance];
}
while(true){
debug(traceExec){
writeln(" ", stack.live);
disassembleInstruction(chunk, ip - &chunk.code[0]);
}
OpCode instruction = readIns();
with(OpCode) opSwitch: final switch(instruction){
case Constant:
Value constant = readConstant();
stack.push(constant);
break;
case True:
stack.push(Value.bln(true));
break;
case False:
stack.push(Value.bln(false));
break;
case Nil:
stack.push(Value.nil());
break;
static foreach(k, op; [ Add: "+", Subtract: "-", Multiply: "*", Divide: "/" ]){
case k:
if(!peek(0).isNum || !peek(1).isNum){
runtimeError("Operands must be numbers.");
return InterpretResult.RuntimeError;
}
double b = stack.pop().getNum;
double a = stack.pop().getNum;
stack.push(Value.num(mixin("a", op, "b")));
break opSwitch;
}
static foreach(k, op; [ NotEqual: "!=", Equal: "==", Greater: ">", GreaterEqual: ">=", Less: "<", LessEqual: "<=" ]){
case k:
Value b = stack.pop();
Value a = stack.pop();
stack.push(Value.bln(compare!op(a, b)));
break opSwitch;
}
case Not:
stack.push(Value.bln(stack.pop().isFalsey));
break;
case Negate:
if(!peek(0).isNum){
runtimeError("Operand must be a number.");
return InterpretResult.RuntimeError;
}
stack.push(Value.num(-stack.pop().getNum));
break;
case Return:
debug printValue(stack.pop());
debug writeln();
return InterpretResult.Ok;
}
}
assert(0);
}
}

View file

@ -6,39 +6,3 @@ template defaultCtor(){
this.tupleof[i] = a; this.tupleof[i] = a;
} }
} }
import std.ascii : isAlpha, isAlphaNum;
bool isAlpha_(dchar c) @nogc nothrow @safe => c.isAlpha || c == '_';
bool isAlphaNum_(dchar c) @nogc nothrow @safe => c.isAlphaNum || c == '_';
T pop(T)(ref T[] arr){
T v = arr.last;
arr.length--;
return v;
}
T shift(T)(ref T[] arr){
T v = arr.first;
arr = arr[1 .. $];
return v;
}
ref T sole(T)(T[] arr) @nogc @safe pure{
assert(arr.length == 1, "Not sole");
return arr[0];
}
ref T first(T)(T[] arr) @nogc @safe pure{
assert(arr.length >= 1, "No first");
return arr[0];
}
ref T second(T)(T[] arr) @nogc @safe pure{
assert(arr.length >= 2, "No second");
return arr[1];
}
ref T third(T)(T[] arr) @nogc @safe pure{
assert(arr.length >= 3, "No third");
return arr[2];
}
ref T last(T)(T[] arr) @nogc @safe pure{
assert(arr.length >= 1, "No last");
return arr[$-1];
}

View file

@ -6,40 +6,26 @@ import common.util;
class Environment{ class Environment{
Environment enclosing; Environment enclosing;
private LoxValue[string] values; private TValue[string] values;
mixin defaultCtor; mixin defaultCtor;
void define(string name, LoxValue value){ void define(string name, TValue value){
values[name] = value; values[name] = value;
} }
LoxValue get(Token name){ TValue get(Token name){
if(LoxValue* value = name.lexeme in values) if(TValue* value = name.lexeme in values)
return *value; return *value;
if(enclosing) if(enclosing)
return enclosing.get(name); return enclosing.get(name);
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
} }
Environment ancestor(uint distance){ void assign(Token name, TValue value){
Environment environment = this; if(name.lexeme in values)
for(uint i = 0; i < distance; i++) return values[name.lexeme] = value;
environment = environment.enclosing;
return environment;
}
LoxValue getAt(uint distance, string name){
return ancestor(distance).values[name];
}
void assign(Token name, LoxValue value){
if(name.lexeme in values){
values[name.lexeme] = value;
return;
}
if(enclosing) if(enclosing)
return enclosing.assign(name, value); return enclosing.assign(name, value);
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
} }
void assignAt(uint distance, Token name, LoxValue value){
ancestor(distance).values[name.lexeme] = value;
}
} }

View file

@ -4,6 +4,8 @@ import std.conv;
import std.stdio; import std.stdio;
import std.meta : AliasSeq; import std.meta : AliasSeq;
import taggedalgebraic;
import jlox.token; import jlox.token;
import jlox.tokentype; import jlox.tokentype;
import common.util; import common.util;
@ -13,17 +15,13 @@ abstract class Expr{
R visit(Assign expr); R visit(Assign expr);
R visit(Binary expr); R visit(Binary expr);
R visit(Call expr); R visit(Call expr);
R visit(Get expr);
R visit(Grouping expr); R visit(Grouping expr);
R visit(Literal expr); R visit(Literal expr);
R visit(Logical expr); R visit(Logical expr);
R visit(Set expr);
R visit(Super expr);
R visit(This expr);
R visit(Unary expr); R visit(Unary expr);
R visit(Variable expr); R visit(Variable expr);
} }
private alias rTypes = AliasSeq!(string, LoxValue, void); private alias rTypes = AliasSeq!(string, TValue);
static foreach(T; rTypes) static foreach(T; rTypes)
abstract T accept(Visitor!T visitor); abstract T accept(Visitor!T visitor);
private template defCtorAndAccept(){ private template defCtorAndAccept(){
@ -49,17 +47,12 @@ abstract class Expr{
Expr[] arguments; Expr[] arguments;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Get : typeof(this){
Expr object;
Token name;
mixin defCtorAndAccept;
}
static class Grouping : typeof(this){ static class Grouping : typeof(this){
Expr expression; Expr expression;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Literal : typeof(this){ static class Literal : typeof(this){
LoxValue value; TValue value;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Logical : typeof(this){ static class Logical : typeof(this){
@ -68,21 +61,6 @@ abstract class Expr{
Expr right; Expr right;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Set : typeof(this){
Expr object;
Token name;
Expr value;
mixin defCtorAndAccept;
}
static class Super : typeof(this){
Token keyword;
Token method;
mixin defCtorAndAccept;
}
static class This : typeof(this){
Token keyword;
mixin defCtorAndAccept;
}
static class Unary : typeof(this){ static class Unary : typeof(this){
Token operator; Token operator;
Expr right; Expr right;
@ -94,3 +72,31 @@ abstract class Expr{
} }
} }
/* class AstPrinter : Expr.Visitor!string{ */
/* string print(Expr expr){ */
/* return expr.accept(this); */
/* } */
/* string parenthesize(Args...)(string name, Args args){ */
/* string s = "(" ~ name; */
/* static foreach(expr; args){ */
/* s ~= " "; */
/* s ~= expr.accept(this); */
/* } */
/* return s ~ ")"; */
/* } */
/* string visit(Expr.Binary expr){ */
/* return parenthesize(expr.operator.lexeme, expr.left, expr.right); */
/* } */
/* string visit(Expr.Grouping expr){ */
/* return parenthesize("group", expr.expression); */
/* } */
/* string visit(Expr.Literal expr){ */
/* if(expr.value.isNil) */
/* return "nil"; */
/* return expr.value.to!string; */
/* } */
/* string visit(Expr.Unary expr){ */
/* return parenthesize(expr.operator.lexeme, expr.right); */
/* } */
/* } */

View file

@ -1,7 +1,6 @@
module jlox.interpreter; module jlox.interpreter;
import std.conv; import std.conv;
import std.exception : enforce;
import std.stdio; import std.stdio;
import std.algorithm; import std.algorithm;
import std.array; import std.array;
@ -12,12 +11,10 @@ import jlox.expr;
import jlox.stmt; import jlox.stmt;
import jlox.token; import jlox.token;
import jlox.tokentype; import jlox.tokentype;
import jlox.token : LoxValue; import jlox.token : TValue;
import jlox.main; import jlox.main;
import jlox.environment; import jlox.environment;
import jlox.loxfunction; import jlox.loxfunction;
import jlox.loxclass;
import jlox.loxinstance;
class RuntimeError : Exception{ class RuntimeError : Exception{
const Token token; const Token token;
@ -27,14 +24,14 @@ class RuntimeError : Exception{
} }
} }
class Return : Exception{ class Return : Exception{
const LoxValue value; const TValue value;
this(LoxValue value){ this(TValue value){
super(null); super(null);
this.value = value; this.value = value;
} }
} }
class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
Environment globals = new Environment(); Environment globals = new Environment();
private Environment environment; private Environment environment;
this(){ this(){
@ -42,24 +39,20 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
import std.datetime.stopwatch; import std.datetime.stopwatch;
auto sw = StopWatch(AutoStart.yes); auto sw = StopWatch(AutoStart.yes);
globals.define("clock", new class LoxCallable{ globals.define("clock", TValue.cal(new class LoxCallable{
int arity() => 0; int arity() => 0;
LoxValue call(Interpreter interpreter, LoxValue[] arguments) => new LoxNum(sw.peek.total!"usecs" / (1000.0 * 1000.0)); TValue call(Interpreter interpreter, TValue[] arguments) => TValue.dbl(sw.peek.total!"usecs" / (1000.0 * 1000.0));
override string toString() const => "<native clock>"; }));
});
version(LoxExtraNativeFuncs){ version(LoxExtraNativeFuncs){
globals.define("sleep", new class LoxCallable{ globals.define("sleep", TValue.cal(new class LoxCallable{
int arity() => 1; int arity() => 1;
import core.thread.osthread; import core.thread.osthread;
LoxValue call(Interpreter interpreter, LoxValue[] arguments){ TValue call(Interpreter interpreter, TValue[] arguments){
if(cast(LoxNum)arguments[0] is null) Thread.sleep(dur!"usecs"(cast(long)(arguments[0].dblValue * 1000 * 1000)));
throw new RuntimeError(null, "Expected number to sleep/1"); return TValue.nil(tvalueNil);
Thread.sleep(dur!"usecs"(cast(long)(cast(LoxNum)arguments[0] * 1000 * 1000)));
return new LoxNil();
} }
override string toString() const => "<native sleep>"; }));
});
} }
} }
void interpret(Stmt[] statements){ void interpret(Stmt[] statements){
@ -83,59 +76,38 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
this.environment = previous; this.environment = previous;
} }
} }
private LoxValue evaluate(Expr expr){ private TValue evaluate(Expr expr){
return expr.accept(this); return expr.accept(this);
} }
private bool isTruthy(LoxValue val){ private bool isTruthy(TValue val){
if(cast(LoxNil)val) switch(val.kind){
return false; case TValue.Kind.nil:
if(auto b = cast(LoxBool)val) return false;
return b; case TValue.Kind.bln:
return true; return val.blnValue;
default:
return true;
}
} }
private bool isEqual(LoxValue a, LoxValue b){ private bool isEqual(TValue a, TValue b){
return a.equals(b); return a == b;
} }
private void checkNumberOperand(Token operator, LoxValue operand){ private void checkNumberOperand(Token operator, TValue operand){
if(cast(LoxNum)operand) if(operand.kind == TValue.Kind.dbl)
return; return;
throw new RuntimeError(operator, "Operand must be a number."); throw new RuntimeError(operator, "Operand must be a number.");
} }
private uint[Expr] locals;
void resolve(Expr expr, uint depth){
locals[expr] = depth;
}
void visit(Stmt.Block stmt){ void visit(Stmt.Block stmt){
executeBlock(stmt.statements, new Environment(environment)); executeBlock(stmt.statements, new Environment(environment));
} }
void visit(Stmt.Class stmt){
LoxValue superclass;
if(stmt.superclass !is null){
superclass = evaluate(stmt.superclass);
enforce(cast(LoxClass)superclass, new RuntimeError(stmt.superclass.name, "Superclass must be a class."));
}
environment.define(stmt.name.lexeme, new LoxNil());
if(stmt.superclass !is null){
environment = new Environment(environment);
environment.define("super", superclass);
}
LoxFunction[string] methods;
foreach(Stmt.Function method; stmt.methods){
LoxFunction func = new LoxFunction(method, environment, method.name.lexeme == "init");
methods[method.name.lexeme] = func;
}
LoxClass cls = new LoxClass(stmt.name.lexeme, cast(LoxClass)superclass, methods);
if(superclass !is null)
environment = environment.enclosing;
environment.assign(stmt.name, cls);
}
void visit(Stmt.Expression stmt){ void visit(Stmt.Expression stmt){
evaluate(stmt.expression); evaluate(stmt.expression);
} }
void visit(Stmt.Function stmt){ void visit(Stmt.Function stmt){
LoxFunction func = new LoxFunction(stmt, environment, false); LoxFunction func = new LoxFunction(stmt, environment);
environment.define(stmt.name.lexeme, func); environment.define(stmt.name.lexeme, TValue.cal(func));
} }
void visit(Stmt.If stmt){ void visit(Stmt.If stmt){
if(isTruthy(evaluate(stmt.condition))) if(isTruthy(evaluate(stmt.condition)))
@ -144,45 +116,41 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
execute(stmt.elseBranch); execute(stmt.elseBranch);
} }
void visit(Stmt.Print stmt){ void visit(Stmt.Print stmt){
version(LoxPrintMultiple){ TValue value = evaluate(stmt.expression);
writeln(stmt.expressions.map!(x => evaluate(x)).map!(x => x.toString).join('\t')); writeln(tvalueToString(value));
} else {
LoxValue value = evaluate(stmt.expression);
writeln(value.toString);
}
} }
void visit(Stmt.Return stmt){ void visit(Stmt.Return stmt){
LoxValue value = stmt.value !is null ? evaluate(stmt.value) : new LoxNil(); TValue value = stmt.value !is null ? evaluate(stmt.value) : TValue.nil(tvalueNil);
throw new Return(value); throw new Return(value);
} }
void visit(Stmt.Var stmt){ void visit(Stmt.Var stmt){
environment.define(stmt.name.lexeme, stmt.initialiser is null ? new LoxNil() : evaluate(stmt.initialiser)); environment.define(stmt.name.lexeme, stmt.initialiser is null ? TValue.nil(tvalueNil) : evaluate(stmt.initialiser));
} }
void visit(Stmt.While stmt){ void visit(Stmt.While stmt){
while(isTruthy(evaluate(stmt.condition))) while(isTruthy(evaluate(stmt.condition)))
execute(stmt.body); execute(stmt.body);
} }
LoxValue visit(Expr.Literal expr){ TValue visit(Expr.Literal expr){
return expr.value; return expr.value;
} }
LoxValue visit(Expr.Grouping expr){ TValue visit(Expr.Grouping expr){
return evaluate(expr.expression); return evaluate(expr.expression);
} }
LoxValue visit(Expr.Unary expr){ TValue visit(Expr.Unary expr){
LoxValue right = evaluate(expr.right); TValue right = evaluate(expr.right);
switch(expr.operator.type){ switch(expr.operator.type){
case TokenType.MINUS: case TokenType.MINUS:
checkNumberOperand(expr.operator, right); checkNumberOperand(expr.operator, right);
return new LoxNum(-cast(LoxNum)right); return TValue.dbl(-right.dblValue);
case TokenType.BANG: case TokenType.BANG:
return new LoxBool(!isTruthy(right)); return TValue.bln(!isTruthy(right));
default: default:
assert(0); assert(0);
} }
} }
LoxValue visit(Expr.Logical expr){ TValue visit(Expr.Logical expr){
LoxValue left = evaluate(expr.left); TValue left = evaluate(expr.left);
if(expr.operator.type == TokenType.OR){ if(expr.operator.type == TokenType.OR){
if(isTruthy(left)) if(isTruthy(left))
return left; return left;
@ -192,84 +160,59 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
} }
return evaluate(expr.right); return evaluate(expr.right);
} }
LoxValue visit(Expr.Set expr){ TValue visit(Expr.Binary expr){
LoxValue object = evaluate(expr.object); TValue left = evaluate(expr.left);
if(!(cast(LoxInstance)object)) TValue right = evaluate(expr.right);
throw new RuntimeError(expr.name, "Only instances have fields."); static string m(TokenType t, string op, string v, string vv){
LoxValue value = evaluate(expr.value); return q{case %s:
(cast(LoxInstance)object).set(expr.name, value); checkNumberOperand(expr.operator, left);
return value; checkNumberOperand(expr.operator, right);
} return TValue.%s( left.%s %s right.%s );
LoxValue visit(Expr.Super expr){ }.format(t, v, vv, op, vv);
uint distance = locals[expr]; }
LoxClass superclass = cast(LoxClass)environment.getAt(distance, "super");
LoxInstance object = cast(LoxInstance)environment.getAt(distance - 1, "this");
LoxFunction method = superclass.findMethod(expr.method.lexeme);
if(method is null)
throw new RuntimeError(expr.method, "Undefined property '" ~ expr.method.lexeme ~ "'.");
return method.bind(object);
}
LoxValue visit(Expr.This expr){
return lookUpVariable(expr.keyword, expr);
}
LoxValue visit(Expr.Binary expr){
LoxValue left = evaluate(expr.left);
LoxValue right = evaluate(expr.right);
with(TokenType) switch(expr.operator.type){ with(TokenType) switch(expr.operator.type){
static foreach(t, op; [ PLUS: "+", MINUS: "-", SLASH: "/", STAR: "*", GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ]){ static foreach(t, op; [ MINUS: "-", SLASH: "/", STAR: "*" ])
case t: mixin(ctEval!(m(t, op, "dbl", "dblValue")));
static if(t == PLUS){
if(cast(LoxStr)left && cast(LoxStr)right) case PLUS:
return new LoxStr(cast(LoxStr)left ~ cast(LoxStr)right); if(left.isDbl && right.isDbl)
version(LoxConcatNonStrings){ return TValue.dbl(left.dblValue + right.dblValue);
if(cast(LoxStr)left || cast(LoxStr)right) else if(left.isStr && right.isStr)
return new LoxStr(left.toString ~ right.toString); return TValue.str(left.strValue ~ right.strValue);
} version(LoxConcatNonStrings){
} if(left.isStr || right.isStr)
checkNumberOperand(expr.operator, left); return TValue.str(tvalueToString(left) ~ tvalueToString(right));
checkNumberOperand(expr.operator, right); }
return (cast(LoxNum)left).opBinary!op(cast(LoxNum)right); checkNumberOperand(expr.operator, left);
} checkNumberOperand(expr.operator, right);
static foreach(t, op; [ GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ])
mixin(ctEval!(m(t, op, "bln", "dblValue")));
case BANG_EQUAL: case BANG_EQUAL:
return new LoxBool(!isEqual(left, right)); return TValue.bln(!isEqual(left, right));
case EQUAL_EQUAL: case EQUAL_EQUAL:
return new LoxBool(isEqual(left, right)); return TValue.bln(isEqual(left, right));
default: default:
assert(0); assert(0);
} }
} }
LoxValue visit(Expr.Call expr){ TValue visit(Expr.Call expr){
LoxValue callee = evaluate(expr.callee); TValue callee = evaluate(expr.callee);
if(!cast(LoxCallable)callee) if(!callee.isCal)
throw new RuntimeError(expr.paren, "Can only call functions and classes."); throw new RuntimeError(expr.paren, "Can only call functions and classes.");
auto arguments = expr.arguments.map!(a => evaluate(a)); auto arguments = expr.arguments.map!(a => evaluate(a));
LoxCallable func = cast(LoxCallable)callee; LoxCallable func = callee.calValue;
if(arguments.length != func.arity()) if(arguments.length != func.arity())
throw new RuntimeError(expr.paren, "Expected " ~ func.arity().to!string ~ " arguments but got " ~ arguments.length.to!string ~ "."); throw new RuntimeError(expr.paren, "Expected " ~ func.arity().to!string ~ " arguments but got " ~ arguments.length.to!string ~ ".");
return func.call(this, arguments.array); return func.call(this, arguments.array);
} }
LoxValue visit(Expr.Get expr){ TValue visit(Expr.Variable expr){
LoxValue object = evaluate(expr.object); return environment.get(expr.name);
if(auto ins = cast(LoxInstance)object)
return ins.get(expr.name);
throw new RuntimeError(expr.name, "Only instances have properties.");
} }
LoxValue visit(Expr.Variable expr){ TValue visit(Expr.Assign expr){
return lookUpVariable(expr.name, expr); TValue value = evaluate(expr.value);
} environment.assign(expr.name, value);
private LoxValue lookUpVariable(Token name, Expr expr){
if(const distance = expr in locals)
return environment.getAt(*distance, name.lexeme);
else
return globals.get(name);
}
LoxValue visit(Expr.Assign expr){
LoxValue value = evaluate(expr.value);
if(const distance = expr in locals)
environment.assignAt(*distance, expr.name, value);
else
globals.assign(expr.name, value);
return value; return value;
} }
} }

View file

@ -1,37 +0,0 @@
module jlox.loxclass;
import jlox.token;
import jlox.interpreter;
import jlox.loxinstance;
import jlox.loxfunction;
import common.util;
class LoxClass : LoxCallable{
package const string name;
private const LoxClass superclass;
private const LoxFunction[string] methods;
mixin defaultCtor;
LoxFunction findMethod(string name) const{
if(auto method = name in methods)
return cast(LoxFunction)*method;
if(superclass !is null)
return superclass.findMethod(name);
return null;
}
int arity(){
if(auto initialiser = findMethod("init"))
return initialiser.arity();
return 0;
}
LoxValue call(Interpreter interpreter, LoxValue[] arguments){
LoxInstance instance = new LoxInstance(this);
LoxFunction initialiser = findMethod("init");
if(initialiser !is null)
initialiser.bind(instance).call(interpreter, arguments);
return instance;
}
override string toString() const => "class";
}

View file

@ -2,44 +2,31 @@ module jlox.loxfunction;
import std.conv; import std.conv;
import common.util;
import jlox.token; import jlox.token;
import jlox.stmt; import jlox.stmt;
import jlox.interpreter; import jlox.interpreter;
import jlox.environment; import jlox.environment;
import jlox.loxinstance; import common.util;
class LoxFunction : LoxCallable{ class LoxFunction : LoxCallable{
private Stmt.Function declaration; private Stmt.Function declaration;
private Environment closure; private Environment closure;
private const bool isInitialiser;
mixin defaultCtor; mixin defaultCtor;
invariant{ invariant{
assert(declaration && closure); assert(declaration && closure);
} }
LoxFunction bind(LoxInstance instance){
Environment environment = new Environment(closure);
environment.define("this", instance);
return new LoxFunction(declaration, environment, isInitialiser);
}
int arity() => declaration.params.length.to!int; int arity() => declaration.params.length.to!int;
LoxValue call(Interpreter interpreter, LoxValue[] arguments){ TValue call(Interpreter interpreter, TValue[] arguments){
Environment environment = new Environment(closure); Environment environment = new Environment(closure);
foreach(i; 0 .. declaration.params.length) foreach(i; 0 .. declaration.params.length)
environment.define(declaration.params[i].lexeme, arguments[i]); environment.define(declaration.params[i].lexeme, arguments[i]);
try{ try{
interpreter.executeBlock(declaration.body, environment); interpreter.executeBlock(declaration.body, environment);
} catch(Return returnValue){ } catch(Return returnValue){
if(isInitialiser) return returnValue.value;
return closure.getAt(0, "this");
return cast(LoxValue)returnValue.value;
} }
if(isInitialiser) return TValue.nil(tvalueNil);
return closure.getAt(0, "this");
return new LoxNil();
} }
override string toString() const => "function";
} }

View file

@ -1,27 +0,0 @@
module jlox.loxinstance;
import std.format : format;
import jlox.token;
import jlox.interpreter;
import jlox.loxclass;
import common.util;
class LoxInstance : LoxValue{
private LoxClass cls;
private LoxValue[string] fields;
mixin defaultCtor;
override string toString() const => "<instance %s>".format(cls.name);
LoxValue get(Token name){
if(auto v = name.lexeme in fields)
return *v;
if(auto method = cls.findMethod(name.lexeme))
return method.bind(this);
throw new RuntimeError(name, "Undefined property '" ~ name.lexeme ~ "'.");
}
void set(Token name, LoxValue value){
fields[name.lexeme] = value;
}
}

View file

@ -14,11 +14,6 @@ import jlox.parser;
import jlox.interpreter; import jlox.interpreter;
import jlox.expr; import jlox.expr;
import jlox.stmt; import jlox.stmt;
import jlox.resolver;
enum RetVal{
success = 0, other = 1, runtime = 2
}
private class MainException : Exception{ private class MainException : Exception{
this(){ this(){
@ -47,10 +42,7 @@ static class Lox{
hadError = true; hadError = true;
} }
static void runtimeError(RuntimeError error){ static void runtimeError(RuntimeError error){
if(error.token) stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]");
stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]");
else
stderr.writeln(error.msg);
hadRuntimeError = true; hadRuntimeError = true;
} }
@ -60,11 +52,7 @@ static class Lox{
Parser parser = new Parser(tokens); Parser parser = new Parser(tokens);
Stmt[] statements = parser.parse(); Stmt[] statements = parser.parse();
if(hadError)
return;
Resolver resolver = new Resolver(interpreter);
resolver.resolve(statements);
if(hadError) if(hadError)
return; return;
@ -89,8 +77,6 @@ static class Lox{
} }
} }
extern(C) int isatty(int);
int main(string[] argv){ int main(string[] argv){
auto args = new Program("lox") auto args = new Program("lox")
.add(new Argument("path").optional.acceptsFiles) .add(new Argument("path").optional.acceptsFiles)
@ -99,21 +85,17 @@ int main(string[] argv){
try{ try{
if(args.arg("path") && args.option("command")){ if(args.arg("path") && args.option("command")){
stderr.writeln("Cant have both path and --cmd"); stderr.writeln("Cant have both path and --cmd");
return RetVal.other; return 3;
} }
if(auto cmd = args.option("command")){ if(auto cmd = args.option("command"))
Lox.run(cmd); Lox.run(cmd);
} else if(auto path = args.arg("path")){ else if(auto path = args.arg("path"))
Lox.runFile(path); Lox.runFile(path);
} else { else
if(isatty(stdin.fileno)) Lox.runPrompt();
Lox.runPrompt();
else
Lox.runFile("/dev/stdin");
}
} catch(MainException rte){ } catch(MainException rte){
return Lox.hadError ? RetVal.other : RetVal.runtime; return Lox.hadError ? 1 : 2;
} }
return RetVal.success; return 0;
} }

View file

@ -81,18 +81,9 @@ class Parser{
} }
private Stmt printStatement(){ private Stmt printStatement(){
version(LoxPrintMultiple){ Expr value = expression();
Expr[] values; consume(TokenType.SEMICOLON, "Expect ';' after value.");
if(!check(TokenType.SEMICOLON)) do { return new Stmt.Print(value);
values ~= expression();
} while(match(TokenType.COMMA));
consume(TokenType.SEMICOLON, "Expect ';' after values.");
return new Stmt.Print(values);
} else {
Expr value = expression();
consume(TokenType.SEMICOLON, "Expect ';' after value.");
return new Stmt.Print(value);
}
} }
private Stmt returnStatement(){ private Stmt returnStatement(){
Token keyword = previous(); Token keyword = previous();
@ -144,7 +135,7 @@ class Parser{
else else
initialiser = expressionStatement(); initialiser = expressionStatement();
Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(new LoxBool(true)) : expression(); Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(TValue.bln(true)) : expression();
consume(TokenType.SEMICOLON, "Expect ';' after loop condition."); consume(TokenType.SEMICOLON, "Expect ';' after loop condition.");
Expr increment; Expr increment;
@ -197,11 +188,8 @@ class Parser{
if(match(TokenType.EQUAL)){ if(match(TokenType.EQUAL)){
Token equals = previous(); Token equals = previous();
Expr value = assignment(); Expr value = assignment();
if(auto v = cast(Expr.Variable)expr){ if(auto v = cast(Expr.Variable)expr)
return new Expr.Assign(v.name, value); return new Expr.Assign(v.name, value);
} else if(auto get = cast(Expr.Get)expr){
return new Expr.Set(get.object, get.name, value);
}
error(equals, "Invalid assignment target."); error(equals, "Invalid assignment target.");
} }
return expr; return expr;
@ -283,14 +271,10 @@ class Parser{
} }
Expr expr = primary(); Expr expr = primary();
while(true){ while(true){
if(match(TokenType.LEFT_PAREN)){ if(match(TokenType.LEFT_PAREN))
expr = finishCall(expr); expr = finishCall(expr);
} else if(match(TokenType.DOT)){ else
Token name = consume(TokenType.IDENTIFIER, "Expect property name after '.'.");
expr = new Expr.Get(expr, name);
} else {
break; break;
}
} }
return expr; return expr;
} }
@ -298,26 +282,18 @@ class Parser{
if(match(TokenType.IDENTIFIER)) if(match(TokenType.IDENTIFIER))
return new Expr.Variable(previous()); return new Expr.Variable(previous());
if(match(TokenType.FALSE)) if(match(TokenType.FALSE))
return new Expr.Literal(new LoxBool(false)); return new Expr.Literal(TValue.bln(false));
if(match(TokenType.TRUE)) if(match(TokenType.TRUE))
return new Expr.Literal(new LoxBool(true)); return new Expr.Literal(TValue.bln(true));
if(match(TokenType.NIL)) if(match(TokenType.NIL))
return new Expr.Literal(new LoxNil()); return new Expr.Literal(tvalueNil);
if(match(TokenType.NUMBER, TokenType.STRING)) if(match(TokenType.NUMBER, TokenType.STRING))
return new Expr.Literal(previous().literal); return new Expr.Literal(previous().literal);
if(match(TokenType.THIS))
return new Expr.This(previous());
if(match(TokenType.LEFT_PAREN)){ if(match(TokenType.LEFT_PAREN)){
Expr expr = expression(); Expr expr = expression();
consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
return new Expr.Grouping(expr); return new Expr.Grouping(expr);
} }
if(match(TokenType.SUPER)){
Token keyword = previous();
consume(TokenType.DOT, "Expect '.' after 'super'.");
Token method = consume(TokenType.IDENTIFIER, "Expect superclass method name.");
return new Expr.Super(keyword, method);
}
throw error(peek(), "Expect expression."); throw error(peek(), "Expect expression.");
} }
private Stmt varDeclaration(){ private Stmt varDeclaration(){
@ -328,28 +304,12 @@ class Parser{
consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); consume(TokenType.SEMICOLON, "Expect ';' after variable declaration.");
return new Stmt.Var(name, initialiser); return new Stmt.Var(name, initialiser);
} }
private Stmt classDeclaration() {
Token name = consume(TokenType.IDENTIFIER, "Expect class name.");
Expr.Variable superclass;
if(match(TokenType.LESS)){
consume(TokenType.IDENTIFIER, "Expect superlcass name.");
superclass = new Expr.Variable(previous);
}
consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
Stmt.Function[] methods;
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
methods ~= fun("method");
consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
return new Stmt.Class(name, superclass, methods);
}
private Stmt declaration(){ private Stmt declaration(){
try { try {
if(match(TokenType.VAR))
return varDeclaration();
if(match(TokenType.FUN)) if(match(TokenType.FUN))
return fun("function"); return fun("function");
if(match(TokenType.CLASS)) if(match(TokenType.VAR))
return classDeclaration(); return varDeclaration();
return statement(); return statement();
} catch(ParseError error){ } catch(ParseError error){
synchronise(); synchronise();

View file

@ -1,198 +0,0 @@
module jlox.resolver;
import std.container.slist;
import std.stdio;
import std.algorithm;
import std.range : enumerate;
import jlox.main;
import jlox.token;
import jlox.stmt;
import jlox.expr;
import jlox.interpreter;
import common.util;
class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
private enum FunctionType{ NONE, FUNCTION, INITIALISER, METHOD }
private enum ClassType{ NONE, CLASS, SUBCLASS }
private Interpreter interpreter;
this(Interpreter interpreter){
this.interpreter = interpreter;
}
private FunctionType currentFunction = FunctionType.NONE;
private ClassType currentClass = ClassType.NONE;
private SList!(bool[string]) scopes;
private void resolve(Stmt stmt) => stmt.accept(this);
private void resolve(Expr expr) => expr.accept(this);
void resolve(Stmt[] statements){
foreach(statement; statements)
resolve(statement);
}
private void resolveLocal(Expr expr, Token name){
foreach(i, scp; scopes[].enumerate){
if(name.lexeme in scp){
interpreter.resolve(expr, cast(uint)i);
return;
}
}
}
private void resolveFunction(Stmt.Function func, FunctionType type){
FunctionType enclosingFunction = currentFunction;
currentFunction = type;
scope(exit)
currentFunction = enclosingFunction;
beginScope();
foreach(param; func.params){
declare(param);
define(param);
}
resolve(func.body);
endScope();
}
private void endScope() => scopes.removeFront();
private void beginScope(){
bool[string] s;
scopes.insertFront(s);
}
private void declare(Token name){
if(scopes.empty)
return;
bool[string] scp = scopes.front;
if(name.lexeme in scp)
Lox.error(name, "Already a variable with this name in this scope.");
scp[name.lexeme] = false;
}
private void define(Token name){
if(scopes.empty)
return;
scopes.front[name.lexeme] = true;
}
void visit(Stmt.Block stmt){
beginScope();
resolve(stmt.statements);
endScope();
}
void visit(Stmt.Class stmt){
ClassType enclosingClass = currentClass;
currentClass = ClassType.CLASS;
scope(exit)
currentClass = enclosingClass;
declare(stmt.name);
define(stmt.name);
if(stmt.superclass !is null){
currentClass = ClassType.SUBCLASS;
if(stmt.name.lexeme == stmt.superclass.name.lexeme)
Lox.error(stmt.superclass.name, "A class can't inherit from itself.");
resolve(stmt.superclass);
}
if(stmt.superclass !is null){
beginScope();
scopes.front["super"] = true;
}
beginScope();
scopes.front["this"] = true;
foreach(method; stmt.methods){
FunctionType declaration = FunctionType.METHOD;
if(method.name.lexeme == "init")
declaration = FunctionType.INITIALISER;
resolveFunction(method, declaration);
}
endScope();
if(stmt.superclass !is null)
endScope();
}
void visit(Stmt.Var stmt){
declare(stmt.name);
if(stmt.initialiser !is null)
resolve(stmt.initialiser);
define(stmt.name);
}
void visit(Stmt.Function stmt){
declare(stmt.name);
define(stmt.name);
resolveFunction(stmt, FunctionType.FUNCTION);
}
void visit(Expr.Variable expr){
if(!scopes.empty && scopes.front.get(expr.name.lexeme, true) == false)
Lox.error(expr.name, "Can't read local variable in its own initializer.");
resolveLocal(expr, expr.name);
}
void visit(Expr.Assign expr){
resolve(expr.value);
resolveLocal(expr, expr.name);
}
void visit(Stmt.Expression stmt){
resolve(stmt.expression);
}
void visit(Stmt.If stmt){
resolve(stmt.condition);
resolve(stmt.thenBranch);
if(stmt.elseBranch !is null)
resolve(stmt.elseBranch);
}
void visit(Stmt.Print stmt){
version(LoxPrintMultiple){
foreach(expr; stmt.expressions)
resolve(expr);
} else {
resolve(stmt.expression);
}
}
void visit(Stmt.Return stmt){
if(currentFunction == FunctionType.NONE)
Lox.error(stmt.keyword, "Can't return from top-level code.");
if(stmt.value !is null){
if(currentFunction == FunctionType.INITIALISER)
Lox.error(stmt.keyword, "Can't return a value from an initializer.");
resolve(stmt.value);
}
}
void visit(Stmt.While stmt){
resolve(stmt.condition);
resolve(stmt.body);
}
void visit(Expr.Binary expr){
resolve(expr.left);
resolve(expr.right);
}
void visit(Expr.Call expr){
resolve(expr.callee);
foreach(argument; expr.arguments)
resolve(argument);
}
void visit(Expr.Get expr){
resolve(expr.object);
}
void visit(Expr.Grouping expr){
resolve(expr.expression);
}
void visit(Expr.Literal expr){}
void visit(Expr.Logical expr){
resolve(expr.left);
resolve(expr.right);
}
void visit(Expr.Set expr){
resolve(expr.value);
resolve(expr.object);
}
void visit(Expr.Super expr){
if(currentClass == ClassType.NONE)
Lox.error(expr.keyword, "Can't use 'super' outside of a class.");
else if (currentClass != ClassType.SUBCLASS)
Lox.error(expr.keyword, "Can't use 'super' in a class with no superclass.");
resolveLocal(expr, expr.keyword);
}
void visit(Expr.This expr){
if(currentClass == ClassType.NONE){
Lox.error(expr.keyword, "Can't use 'this' outside of a class.");
return;
}
resolveLocal(expr, expr.keyword);
}
void visit(Expr.Unary expr){
resolve(expr.right);
}
}

View file

@ -6,9 +6,11 @@ import std.conv;
import jlox.token; import jlox.token;
import jlox.tokentype; import jlox.tokentype;
import jlox.main; import jlox.main;
import common.util;
class Scanner{ private bool isAlpha_(dchar c) => c.isAlpha || c == '_';
private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_';
class Scanner {
private string source; private string source;
private Token[] tokens; private Token[] tokens;
private uint start = 0; private uint start = 0;
@ -28,7 +30,7 @@ class Scanner{
current++; current++;
return true; return true;
} }
private void addToken(TokenType type, LoxValue literal = new LoxNil()){ private void addToken(TokenType type, TValue literal = TValue.nil(tvalueNil)){
string text = source[start .. current]; string text = source[start .. current];
tokens ~= new Token(type, text, literal, line); tokens ~= new Token(type, text, literal, line);
} }
@ -52,7 +54,7 @@ class Scanner{
} }
advance(); advance();
string value = source[start + 1 .. current -1]; string value = source[start + 1 .. current -1];
addToken(TokenType.STRING, new LoxStr(value)); addToken(TokenType.STRING, TValue.str(value));
} }
private void number(){ private void number(){
while(peek().isDigit) while(peek().isDigit)
@ -62,7 +64,7 @@ class Scanner{
while(peek().isDigit) while(peek().isDigit)
advance(); advance();
} }
addToken(TokenType.NUMBER, new LoxNum(source[start .. current].to!double)); addToken(TokenType.NUMBER, TValue.dbl(source[start .. current].to!double));
} }
private TokenType keywords(string word){ private TokenType keywords(string word){
with(TokenType) switch(word){ with(TokenType) switch(word){

View file

@ -12,7 +12,6 @@ import jlox.expr;
abstract class Stmt{ abstract class Stmt{
interface Visitor(R){ interface Visitor(R){
R visit(Block expr); R visit(Block expr);
R visit(Class expr);
R visit(Expression expr); R visit(Expression expr);
R visit(Function expr); R visit(Function expr);
R visit(If expr); R visit(If expr);
@ -34,12 +33,6 @@ abstract class Stmt{
Stmt[] statements; Stmt[] statements;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Class : typeof(this){
Token name;
Expr.Variable superclass;
Function[] methods;
mixin defCtorAndAccept;
}
static class Expression : typeof(this){ static class Expression : typeof(this){
Expr expression; Expr expression;
mixin defCtorAndAccept; mixin defCtorAndAccept;
@ -57,10 +50,7 @@ abstract class Stmt{
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Print : typeof(this){ static class Print : typeof(this){
version(LoxPrintMultiple) Expr expression;
Expr[] expressions;
else
Expr expression;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Return : typeof(this){ static class Return : typeof(this){

View file

@ -1,70 +1,48 @@
module jlox.token; module jlox.token;
import std.stdio;
import std.conv; import std.conv;
import std.format : format;
import common.util; import taggedalgebraic;
import jlox.tokentype; import jlox.tokentype;
import jlox.interpreter; import jlox.interpreter;
import jlox.loxclass;
import jlox.loxinstance;
import jlox.loxfunction;
abstract interface LoxValue{ interface LoxCallable{
string toString() const;
final bool equals(LoxValue v){
if(this.classinfo != v.classinfo)
return false;
else if(auto s = cast(LoxStr)this)
return s.str == (cast(LoxStr)v).str;
else if(auto n = cast(LoxNum)this)
return n.num == (cast(LoxNum)v).num;
else if(auto b = cast(LoxBool)this)
return b.bln == (cast(LoxBool)v).bln;
return this == v;
};
}
interface LoxCallable : LoxValue{
int arity(); int arity();
LoxValue call(Interpreter interpreter, LoxValue[] arguments); TValue call(Interpreter interpreter, TValue[] arguments);
} }
class LoxStr : LoxValue{ private struct Value{
immutable string str; string str;
alias str this; double dbl;
mixin defaultCtor; bool bln;
override string toString() const => str; LoxCallable cal;
struct Nil{}
Nil nil;
} }
class LoxBool : LoxValue{ alias TValue = TaggedUnion!Value;
immutable bool bln; immutable tvalueNil = Value.Nil();
alias bln this; string tvalueToString(TValue val){
mixin defaultCtor; final switch(val.kind){
override string toString() const => bln.to!string; case TValue.Kind.str:
} return val.strValue;
class LoxNum : LoxValue{ case TValue.Kind.dbl:
immutable double num; return val.dblValue.to!string;
alias num this; case TValue.Kind.bln:
mixin defaultCtor; return val.blnValue ? "true" : "false";
override string toString() const => num.to!string; case TValue.Kind.cal:
auto opBinary(string op)(const LoxNum rhs) const{ return "<function>";
mixin("auto v = num", op, "rhs.num;"); case TValue.Kind.nil:
static if(is(typeof(v) == bool)) return "nil";
return new LoxBool(v);
else
return new LoxNum(v);
} }
} }
class LoxNil : LoxValue{
override string toString() const => "<nil>";
}
class Token{ class Token{
TokenType type; TokenType type;
string lexeme; string lexeme;
LoxValue literal; TValue literal;
int line; int line;
this(TokenType type, string lexeme, LoxValue literal, int line) { this(TokenType type, string lexeme, TValue literal, int line) {
this.type = type; this.type = type;
this.lexeme = lexeme; this.lexeme = lexeme;
this.literal = literal; this.literal = literal;

View file

@ -1,56 +1,22 @@
#!/bin/env rdmd #!/bin/env rdmd
import std.stdio;
import std.process; import std.process;
import std.concurrency;
import std.conv; import std.conv;
import std.string, std.format;
import std.algorithm, std.range;
void main(){ void main(){
"./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n"); string fib(uint n){
"./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n"); string r = "";
"./test/closure.lox".match("1\n2\n"); double a = 0;
"./test/scope.lox".match("global first first second first ".replace(' ', '\n').repeat(2).join("\n")); double temp;
"./test/fib_for.lox".match(fib(6765)); for(double b = 1; a <= n; b = temp + b){
"./test/fib_recursive.lox".match(fib(34)); r ~= a.to!string ~ "\n";
"./test/fib_closure.lox".match(fib(34)); temp = a;
"./test/class.lox".match("The German chocolate cake is delicious!\n"); a = b;
"./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n"); }
return r;
"./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");
}
enum RetVal{
success = 0, other = 1, runtime = 2
}
string fib(uint n){
string r = "";
double a = 0;
double temp;
for(double b = 1; a <= n; b = temp + b){
r ~= a.to!string ~ "\n";
temp = a;
a = b;
} }
return r; assert([ "./lox", "test/fib21.lox" ].execute.output == fib(6765));
} assert([ "./lox", "test/fib10.lox" ].execute.output == fib(34));
auto run(string file) => [ "./lox", file ].execute; assert([ "./lox", "test/closure.lox" ].execute.output == "1\n2\n");
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;
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));
} }

View file

@ -1,21 +0,0 @@
class Cake{
init(goodness){
this.adjective = this.rate(goodness);
}
rate(goodness){
if(goodness)
return "delicious";
else
return "horrendous";
}
taste(){
print "The " + this.flavor + " cake is " + this.adjective + "!";
}
}
var cake = Cake(true);
cake.flavor = "German chocolate";
var t = cake.taste;
t();

View file

@ -1,6 +0,0 @@
fun bad(){
var a = "first";
var a = "second";
}

View file

@ -1,3 +0,0 @@
return ":)";

View file

@ -1,3 +0,0 @@
foo

View file

@ -1,3 +0,0 @@
var x = x + 1;

View file

@ -1,3 +0,0 @@
super.notEvenInAClass();

View file

@ -1,8 +0,0 @@
class Eclair{
cook(){
super.cook();
print "Pipe full of crème pâtissière.";
}
}

View file

@ -1,3 +0,0 @@
print hello;

View file

@ -1,18 +0,0 @@
fun fibonacci(){
var a = 0;
var b = 1;
fun f(){
var next = a;
a = b;
b = next + b;
return next;
}
return f;
}
var nextFibonacci = fibonacci();
for(var i = 0; i < 10; i = i + 1)
print nextFibonacci();

View file

@ -1,17 +0,0 @@
print 2 - 1;
print 1 + 1;
print 6 / 2;
print 2 * 2;
print 2 * 3 - 1;
print 1 + 2 + 3;
print 1 + 2 * 3;
print 2 <= 3 or false;
print 1 > 2;
print 1 == 1;
print 1 != 2;
print "hello, " + "world";

View file

@ -1,19 +0,0 @@
var a = "global";
fun f(){
print a;
var a = "first";
print a;
{
print a;
var a = "second";
print a;
}
print a;
}
f();
print "";
f();

View file

@ -1,12 +0,0 @@
fun scream(extra){
extra = extra or "!";
print "AAAA" + extra;
}
print nil or true;
scream(false);
scream(nil);
scream("?");

View file

@ -1,36 +0,0 @@
class Doughnut{
cook(){
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut{
cook(){
super.cook();
print "Pipe full of custard and coat with chocolate.";
}
}
BostonCream().cook();
class A{
method(){
print "A method";
}
}
class B < A{
method(){
print "B method";
}
test(){
super.method();
}
}
class C < B{}
C().test();