diff --git a/.gitignore b/.gitignore index 5a66296..a8ba56d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ lox-test-* *.lst .msc/ -test/test.lox diff --git a/dub.sdl b/dub.sdl index 90eb9b4..58a534e 100644 --- a/dub.sdl +++ b/dub.sdl @@ -4,19 +4,13 @@ authors "tanya" copyright "Copyright © 2025, tanya" license "MPL-2.0" dependency "commandr" version="~>1.1.0" -dependency "colored" version="~>0.0.33" +dependency "taggedalgebraic" version="~>0.11.23" targetType "executable" -sourcePaths -configuration "clox" { - /* debugVersions "traceExec" */ - debugVersions "printCode" - targetType "executable" - sourcePaths "src/clox" "src/common" - buildRequirements "requireBoundsCheck" "requireContracts" -} +buildRequirements "requireBoundsCheck" "requireContracts" + +versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" + configuration "jlox" { - targetType "executable" sourcePaths "src/jlox" "src/common" - versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple" - buildRequirements "requireBoundsCheck" "requireContracts" } + diff --git a/dub.selections.json b/dub.selections.json index ead0009..6b90314 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,9 +1,7 @@ { "fileVersion": 1, "versions": { - "colored": "0.0.33", "commandr": "1.1.0", - "taggedalgebraic": "0.11.23", - "unit-threaded": "2.2.3" + "taggedalgebraic": "0.11.23" } } diff --git a/src/clox/chunk.d b/src/clox/chunk.d deleted file mode 100644 index e41bcaf..0000000 --- a/src/clox/chunk.d +++ /dev/null @@ -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 - } -} - diff --git a/src/clox/compiler.d b/src/clox/compiler.d deleted file mode 100644 index 1107ca5..0000000 --- a/src/clox/compiler.d +++ /dev/null @@ -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; - } -} - - diff --git a/src/clox/container/int24.d b/src/clox/container/int24.d deleted file mode 100644 index 406a933..0000000 --- a/src/clox/container/int24.d +++ /dev/null @@ -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); -} - diff --git a/src/clox/container/rle.d b/src/clox/container/rle.d deleted file mode 100644 index f6e117a..0000000 --- a/src/clox/container/rle.d +++ /dev/null @@ -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); - } -} - diff --git a/src/clox/container/stack.d b/src/clox/container/stack.d deleted file mode 100644 index 9dabed1..0000000 --- a/src/clox/container/stack.d +++ /dev/null @@ -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)]; - } -} - diff --git a/src/clox/container/varint.d b/src/clox/container/varint.d deleted file mode 100644 index d1d467b..0000000 --- a/src/clox/container/varint.d +++ /dev/null @@ -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); - } -} - diff --git a/src/clox/container/vartype.d b/src/clox/container/vartype.d deleted file mode 100644 index 1a27cc9..0000000 --- a/src/clox/container/vartype.d +++ /dev/null @@ -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); -} - diff --git a/src/clox/dbg.d b/src/clox/dbg.d deleted file mode 100644 index 570efc5..0000000 --- a/src/clox/dbg.d +++ /dev/null @@ -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; - } -} - diff --git a/src/clox/emitter.d b/src/clox/emitter.d deleted file mode 100644 index 116f504..0000000 --- a/src/clox/emitter.d +++ /dev/null @@ -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; - } -} - diff --git a/src/clox/main.d b/src/clox/main.d deleted file mode 100644 index 19e0fe7..0000000 --- a/src/clox/main.d +++ /dev/null @@ -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"); -} - diff --git a/src/clox/parser.d b/src/clox/parser.d deleted file mode 100644 index 24bbdbd..0000000 --- a/src/clox/parser.d +++ /dev/null @@ -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); - } - } -} diff --git a/src/clox/parserules.d b/src/clox/parserules.d deleted file mode 100644 index a8bff9e..0000000 --- a/src/clox/parserules.d +++ /dev/null @@ -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), -]; - diff --git a/src/clox/scanner.d b/src/clox/scanner.d deleted file mode 100644 index d916993..0000000 --- a/src/clox/scanner.d +++ /dev/null @@ -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."); - } -} - diff --git a/src/clox/util.d b/src/clox/util.d deleted file mode 100644 index 45c0945..0000000 --- a/src/clox/util.d +++ /dev/null @@ -1,6 +0,0 @@ -module clox.util; - -import std.stdio; -import std.traits : isUnsigned; -import std.container.array; - diff --git a/src/clox/value.d b/src/clox/value.d deleted file mode 100644 index 1914616..0000000 --- a/src/clox/value.d +++ /dev/null @@ -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); - } -} - diff --git a/src/clox/vm.d b/src/clox/vm.d deleted file mode 100644 index 485a626..0000000 --- a/src/clox/vm.d +++ /dev/null @@ -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); - } -} - diff --git a/src/common/util.d b/src/common/util.d index a6876ba..14e0917 100644 --- a/src/common/util.d +++ b/src/common/util.d @@ -6,39 +6,3 @@ template defaultCtor(){ 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]; -} - diff --git a/src/jlox/environment.d b/src/jlox/environment.d index b1d32bd..06023dc 100644 --- a/src/jlox/environment.d +++ b/src/jlox/environment.d @@ -6,40 +6,26 @@ import common.util; class Environment{ Environment enclosing; - private LoxValue[string] values; + private TValue[string] values; mixin defaultCtor; - void define(string name, LoxValue value){ + void define(string name, TValue value){ values[name] = value; } - LoxValue get(Token name){ - if(LoxValue* value = name.lexeme in values) + TValue get(Token name){ + if(TValue* value = name.lexeme in values) return *value; if(enclosing) return enclosing.get(name); throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); } - Environment ancestor(uint distance){ - Environment environment = this; - for(uint i = 0; i < distance; i++) - 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; - } + void assign(Token name, TValue value){ + if(name.lexeme in values) + return values[name.lexeme] = value; if(enclosing) return enclosing.assign(name, value); throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); } - void assignAt(uint distance, Token name, LoxValue value){ - ancestor(distance).values[name.lexeme] = value; - } } diff --git a/src/jlox/expr.d b/src/jlox/expr.d index f95a4ff..2aea4b1 100644 --- a/src/jlox/expr.d +++ b/src/jlox/expr.d @@ -4,6 +4,8 @@ import std.conv; import std.stdio; import std.meta : AliasSeq; +import taggedalgebraic; + import jlox.token; import jlox.tokentype; import common.util; @@ -13,17 +15,13 @@ abstract class Expr{ R visit(Assign expr); R visit(Binary expr); R visit(Call expr); - R visit(Get expr); R visit(Grouping expr); R visit(Literal expr); R visit(Logical expr); - R visit(Set expr); - R visit(Super expr); - R visit(This expr); R visit(Unary expr); R visit(Variable expr); } - private alias rTypes = AliasSeq!(string, LoxValue, void); + private alias rTypes = AliasSeq!(string, TValue); static foreach(T; rTypes) abstract T accept(Visitor!T visitor); private template defCtorAndAccept(){ @@ -49,17 +47,12 @@ abstract class Expr{ Expr[] arguments; mixin defCtorAndAccept; } - static class Get : typeof(this){ - Expr object; - Token name; - mixin defCtorAndAccept; - } static class Grouping : typeof(this){ Expr expression; mixin defCtorAndAccept; } static class Literal : typeof(this){ - LoxValue value; + TValue value; mixin defCtorAndAccept; } static class Logical : typeof(this){ @@ -68,21 +61,6 @@ abstract class Expr{ Expr right; 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){ Token operator; 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); */ +/* } */ +/* } */ + diff --git a/src/jlox/interpreter.d b/src/jlox/interpreter.d index 15870bc..d248eda 100644 --- a/src/jlox/interpreter.d +++ b/src/jlox/interpreter.d @@ -1,7 +1,6 @@ module jlox.interpreter; import std.conv; -import std.exception : enforce; import std.stdio; import std.algorithm; import std.array; @@ -12,12 +11,10 @@ import jlox.expr; import jlox.stmt; import jlox.token; import jlox.tokentype; -import jlox.token : LoxValue; +import jlox.token : TValue; import jlox.main; import jlox.environment; import jlox.loxfunction; -import jlox.loxclass; -import jlox.loxinstance; class RuntimeError : Exception{ const Token token; @@ -27,14 +24,14 @@ class RuntimeError : Exception{ } } class Return : Exception{ - const LoxValue value; - this(LoxValue value){ + const TValue value; + this(TValue value){ super(null); this.value = value; } } -class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { +class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue { Environment globals = new Environment(); private Environment environment; this(){ @@ -42,24 +39,20 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { import std.datetime.stopwatch; auto sw = StopWatch(AutoStart.yes); - globals.define("clock", new class LoxCallable{ + globals.define("clock", TValue.cal(new class LoxCallable{ int arity() => 0; - LoxValue call(Interpreter interpreter, LoxValue[] arguments) => new LoxNum(sw.peek.total!"usecs" / (1000.0 * 1000.0)); - override string toString() const => ""; - }); + TValue call(Interpreter interpreter, TValue[] arguments) => TValue.dbl(sw.peek.total!"usecs" / (1000.0 * 1000.0)); + })); version(LoxExtraNativeFuncs){ - globals.define("sleep", new class LoxCallable{ + globals.define("sleep", TValue.cal(new class LoxCallable{ int arity() => 1; import core.thread.osthread; - LoxValue call(Interpreter interpreter, LoxValue[] arguments){ - if(cast(LoxNum)arguments[0] is null) - throw new RuntimeError(null, "Expected number to sleep/1"); - Thread.sleep(dur!"usecs"(cast(long)(cast(LoxNum)arguments[0] * 1000 * 1000))); - return new LoxNil(); + TValue call(Interpreter interpreter, TValue[] arguments){ + Thread.sleep(dur!"usecs"(cast(long)(arguments[0].dblValue * 1000 * 1000))); + return TValue.nil(tvalueNil); } - override string toString() const => ""; - }); + })); } } void interpret(Stmt[] statements){ @@ -83,59 +76,38 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { this.environment = previous; } } - private LoxValue evaluate(Expr expr){ + private TValue evaluate(Expr expr){ return expr.accept(this); } - private bool isTruthy(LoxValue val){ - if(cast(LoxNil)val) - return false; - if(auto b = cast(LoxBool)val) - return b; - return true; + private bool isTruthy(TValue val){ + switch(val.kind){ + case TValue.Kind.nil: + return false; + case TValue.Kind.bln: + return val.blnValue; + default: + return true; + } } - private bool isEqual(LoxValue a, LoxValue b){ - return a.equals(b); + private bool isEqual(TValue a, TValue b){ + return a == b; } - private void checkNumberOperand(Token operator, LoxValue operand){ - if(cast(LoxNum)operand) + private void checkNumberOperand(Token operator, TValue operand){ + if(operand.kind == TValue.Kind.dbl) return; 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){ 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){ evaluate(stmt.expression); } void visit(Stmt.Function stmt){ - LoxFunction func = new LoxFunction(stmt, environment, false); - environment.define(stmt.name.lexeme, func); + LoxFunction func = new LoxFunction(stmt, environment); + environment.define(stmt.name.lexeme, TValue.cal(func)); } void visit(Stmt.If stmt){ if(isTruthy(evaluate(stmt.condition))) @@ -144,45 +116,41 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { execute(stmt.elseBranch); } void visit(Stmt.Print stmt){ - version(LoxPrintMultiple){ - writeln(stmt.expressions.map!(x => evaluate(x)).map!(x => x.toString).join('\t')); - } else { - LoxValue value = evaluate(stmt.expression); - writeln(value.toString); - } + TValue value = evaluate(stmt.expression); + writeln(tvalueToString(value)); } 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); } 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){ while(isTruthy(evaluate(stmt.condition))) execute(stmt.body); } - LoxValue visit(Expr.Literal expr){ + TValue visit(Expr.Literal expr){ return expr.value; } - LoxValue visit(Expr.Grouping expr){ + TValue visit(Expr.Grouping expr){ return evaluate(expr.expression); } - LoxValue visit(Expr.Unary expr){ - LoxValue right = evaluate(expr.right); + TValue visit(Expr.Unary expr){ + TValue right = evaluate(expr.right); switch(expr.operator.type){ case TokenType.MINUS: checkNumberOperand(expr.operator, right); - return new LoxNum(-cast(LoxNum)right); + return TValue.dbl(-right.dblValue); case TokenType.BANG: - return new LoxBool(!isTruthy(right)); + return TValue.bln(!isTruthy(right)); default: assert(0); } } - LoxValue visit(Expr.Logical expr){ - LoxValue left = evaluate(expr.left); + TValue visit(Expr.Logical expr){ + TValue left = evaluate(expr.left); if(expr.operator.type == TokenType.OR){ if(isTruthy(left)) return left; @@ -192,84 +160,59 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue { } return evaluate(expr.right); } - LoxValue visit(Expr.Set expr){ - LoxValue object = evaluate(expr.object); - if(!(cast(LoxInstance)object)) - throw new RuntimeError(expr.name, "Only instances have fields."); - LoxValue value = evaluate(expr.value); - (cast(LoxInstance)object).set(expr.name, value); - return value; - } - LoxValue visit(Expr.Super expr){ - 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); + TValue visit(Expr.Binary expr){ + TValue left = evaluate(expr.left); + TValue right = evaluate(expr.right); + static string m(TokenType t, string op, string v, string vv){ + return q{case %s: + checkNumberOperand(expr.operator, left); + checkNumberOperand(expr.operator, right); + return TValue.%s( left.%s %s right.%s ); + }.format(t, v, vv, op, vv); + } with(TokenType) switch(expr.operator.type){ - static foreach(t, op; [ PLUS: "+", MINUS: "-", SLASH: "/", STAR: "*", GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ]){ - case t: - static if(t == PLUS){ - if(cast(LoxStr)left && cast(LoxStr)right) - return new LoxStr(cast(LoxStr)left ~ cast(LoxStr)right); - version(LoxConcatNonStrings){ - if(cast(LoxStr)left || cast(LoxStr)right) - return new LoxStr(left.toString ~ right.toString); - } - } - checkNumberOperand(expr.operator, left); - checkNumberOperand(expr.operator, right); - return (cast(LoxNum)left).opBinary!op(cast(LoxNum)right); - } + static foreach(t, op; [ MINUS: "-", SLASH: "/", STAR: "*" ]) + mixin(ctEval!(m(t, op, "dbl", "dblValue"))); + + case PLUS: + if(left.isDbl && right.isDbl) + return TValue.dbl(left.dblValue + right.dblValue); + else if(left.isStr && right.isStr) + return TValue.str(left.strValue ~ right.strValue); + version(LoxConcatNonStrings){ + if(left.isStr || right.isStr) + return TValue.str(tvalueToString(left) ~ tvalueToString(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: - return new LoxBool(!isEqual(left, right)); + return TValue.bln(!isEqual(left, right)); case EQUAL_EQUAL: - return new LoxBool(isEqual(left, right)); + return TValue.bln(isEqual(left, right)); default: - assert(0); + assert(0); } } - LoxValue visit(Expr.Call expr){ - LoxValue callee = evaluate(expr.callee); - if(!cast(LoxCallable)callee) + TValue visit(Expr.Call expr){ + TValue callee = evaluate(expr.callee); + if(!callee.isCal) throw new RuntimeError(expr.paren, "Can only call functions and classes."); auto arguments = expr.arguments.map!(a => evaluate(a)); - LoxCallable func = cast(LoxCallable)callee; + LoxCallable func = callee.calValue; if(arguments.length != func.arity()) throw new RuntimeError(expr.paren, "Expected " ~ func.arity().to!string ~ " arguments but got " ~ arguments.length.to!string ~ "."); return func.call(this, arguments.array); } - LoxValue visit(Expr.Get expr){ - LoxValue object = evaluate(expr.object); - if(auto ins = cast(LoxInstance)object) - return ins.get(expr.name); - throw new RuntimeError(expr.name, "Only instances have properties."); + TValue visit(Expr.Variable expr){ + return environment.get(expr.name); } - LoxValue visit(Expr.Variable expr){ - return lookUpVariable(expr.name, expr); - } - 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); + TValue visit(Expr.Assign expr){ + TValue value = evaluate(expr.value); + environment.assign(expr.name, value); return value; } } diff --git a/src/jlox/loxclass.d b/src/jlox/loxclass.d deleted file mode 100644 index 44be38a..0000000 --- a/src/jlox/loxclass.d +++ /dev/null @@ -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"; -} - diff --git a/src/jlox/loxfunction.d b/src/jlox/loxfunction.d index 504369e..85bbb32 100644 --- a/src/jlox/loxfunction.d +++ b/src/jlox/loxfunction.d @@ -2,44 +2,31 @@ module jlox.loxfunction; import std.conv; -import common.util; import jlox.token; import jlox.stmt; import jlox.interpreter; import jlox.environment; -import jlox.loxinstance; +import common.util; class LoxFunction : LoxCallable{ private Stmt.Function declaration; private Environment closure; - private const bool isInitialiser; mixin defaultCtor; invariant{ 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; - LoxValue call(Interpreter interpreter, LoxValue[] arguments){ + TValue call(Interpreter interpreter, TValue[] arguments){ Environment environment = new Environment(closure); foreach(i; 0 .. declaration.params.length) environment.define(declaration.params[i].lexeme, arguments[i]); try{ interpreter.executeBlock(declaration.body, environment); } catch(Return returnValue){ - if(isInitialiser) - return closure.getAt(0, "this"); - return cast(LoxValue)returnValue.value; + return returnValue.value; } - if(isInitialiser) - return closure.getAt(0, "this"); - return new LoxNil(); + return TValue.nil(tvalueNil); } - override string toString() const => "function"; } diff --git a/src/jlox/loxinstance.d b/src/jlox/loxinstance.d deleted file mode 100644 index f30d858..0000000 --- a/src/jlox/loxinstance.d +++ /dev/null @@ -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 => "".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; - } -} - diff --git a/src/jlox/main.d b/src/jlox/main.d index 17f176a..51e0e33 100644 --- a/src/jlox/main.d +++ b/src/jlox/main.d @@ -14,11 +14,6 @@ import jlox.parser; import jlox.interpreter; import jlox.expr; import jlox.stmt; -import jlox.resolver; - -enum RetVal{ - success = 0, other = 1, runtime = 2 -} private class MainException : Exception{ this(){ @@ -47,10 +42,7 @@ static class Lox{ hadError = true; } static void runtimeError(RuntimeError error){ - if(error.token) - stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]"); - else - stderr.writeln(error.msg); + stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]"); hadRuntimeError = true; } @@ -60,11 +52,7 @@ static class Lox{ Parser parser = new Parser(tokens); Stmt[] statements = parser.parse(); - if(hadError) - return; - Resolver resolver = new Resolver(interpreter); - resolver.resolve(statements); if(hadError) return; @@ -89,8 +77,6 @@ static class Lox{ } } -extern(C) int isatty(int); - int main(string[] argv){ auto args = new Program("lox") .add(new Argument("path").optional.acceptsFiles) @@ -99,21 +85,17 @@ int main(string[] argv){ try{ if(args.arg("path") && args.option("command")){ 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); - } else if(auto path = args.arg("path")){ + else if(auto path = args.arg("path")) Lox.runFile(path); - } else { - if(isatty(stdin.fileno)) - Lox.runPrompt(); - else - Lox.runFile("/dev/stdin"); - } + else + Lox.runPrompt(); } catch(MainException rte){ - return Lox.hadError ? RetVal.other : RetVal.runtime; + return Lox.hadError ? 1 : 2; } - return RetVal.success; + return 0; } diff --git a/src/jlox/parser.d b/src/jlox/parser.d index a314992..6ebc002 100644 --- a/src/jlox/parser.d +++ b/src/jlox/parser.d @@ -81,18 +81,9 @@ class Parser{ } private Stmt printStatement(){ - version(LoxPrintMultiple){ - Expr[] values; - if(!check(TokenType.SEMICOLON)) do { - 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); - } + Expr value = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after value."); + return new Stmt.Print(value); } private Stmt returnStatement(){ Token keyword = previous(); @@ -144,7 +135,7 @@ class Parser{ else 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."); Expr increment; @@ -197,11 +188,8 @@ class Parser{ if(match(TokenType.EQUAL)){ Token equals = previous(); Expr value = assignment(); - if(auto v = cast(Expr.Variable)expr){ + if(auto v = cast(Expr.Variable)expr) 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."); } return expr; @@ -283,14 +271,10 @@ class Parser{ } Expr expr = primary(); while(true){ - if(match(TokenType.LEFT_PAREN)){ + if(match(TokenType.LEFT_PAREN)) expr = finishCall(expr); - } else if(match(TokenType.DOT)){ - Token name = consume(TokenType.IDENTIFIER, "Expect property name after '.'."); - expr = new Expr.Get(expr, name); - } else { + else break; - } } return expr; } @@ -298,26 +282,18 @@ class Parser{ if(match(TokenType.IDENTIFIER)) return new Expr.Variable(previous()); if(match(TokenType.FALSE)) - return new Expr.Literal(new LoxBool(false)); + return new Expr.Literal(TValue.bln(false)); if(match(TokenType.TRUE)) - return new Expr.Literal(new LoxBool(true)); + return new Expr.Literal(TValue.bln(true)); if(match(TokenType.NIL)) - return new Expr.Literal(new LoxNil()); + return new Expr.Literal(tvalueNil); if(match(TokenType.NUMBER, TokenType.STRING)) return new Expr.Literal(previous().literal); - if(match(TokenType.THIS)) - return new Expr.This(previous()); if(match(TokenType.LEFT_PAREN)){ Expr expr = expression(); consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); 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."); } private Stmt varDeclaration(){ @@ -328,28 +304,12 @@ class Parser{ consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); 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(){ try { - if(match(TokenType.VAR)) - return varDeclaration(); if(match(TokenType.FUN)) return fun("function"); - if(match(TokenType.CLASS)) - return classDeclaration(); + if(match(TokenType.VAR)) + return varDeclaration(); return statement(); } catch(ParseError error){ synchronise(); diff --git a/src/jlox/resolver.d b/src/jlox/resolver.d deleted file mode 100644 index c9dbf81..0000000 --- a/src/jlox/resolver.d +++ /dev/null @@ -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); - } -} - diff --git a/src/jlox/scanner.d b/src/jlox/scanner.d index 8da932f..5d0a412 100644 --- a/src/jlox/scanner.d +++ b/src/jlox/scanner.d @@ -6,9 +6,11 @@ import std.conv; import jlox.token; import jlox.tokentype; 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 Token[] tokens; private uint start = 0; @@ -28,7 +30,7 @@ class Scanner{ current++; 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]; tokens ~= new Token(type, text, literal, line); } @@ -52,7 +54,7 @@ class Scanner{ } advance(); string value = source[start + 1 .. current -1]; - addToken(TokenType.STRING, new LoxStr(value)); + addToken(TokenType.STRING, TValue.str(value)); } private void number(){ while(peek().isDigit) @@ -62,7 +64,7 @@ class Scanner{ while(peek().isDigit) 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){ with(TokenType) switch(word){ diff --git a/src/jlox/stmt.d b/src/jlox/stmt.d index 64d35ae..ad701c8 100644 --- a/src/jlox/stmt.d +++ b/src/jlox/stmt.d @@ -12,7 +12,6 @@ import jlox.expr; abstract class Stmt{ interface Visitor(R){ R visit(Block expr); - R visit(Class expr); R visit(Expression expr); R visit(Function expr); R visit(If expr); @@ -34,12 +33,6 @@ abstract class Stmt{ Stmt[] statements; mixin defCtorAndAccept; } - static class Class : typeof(this){ - Token name; - Expr.Variable superclass; - Function[] methods; - mixin defCtorAndAccept; - } static class Expression : typeof(this){ Expr expression; mixin defCtorAndAccept; @@ -57,10 +50,7 @@ abstract class Stmt{ mixin defCtorAndAccept; } static class Print : typeof(this){ - version(LoxPrintMultiple) - Expr[] expressions; - else - Expr expression; + Expr expression; mixin defCtorAndAccept; } static class Return : typeof(this){ diff --git a/src/jlox/token.d b/src/jlox/token.d index dcbf5a3..fb2c117 100644 --- a/src/jlox/token.d +++ b/src/jlox/token.d @@ -1,70 +1,48 @@ module jlox.token; -import std.stdio; import std.conv; -import std.format : format; -import common.util; +import taggedalgebraic; + import jlox.tokentype; import jlox.interpreter; -import jlox.loxclass; -import jlox.loxinstance; -import jlox.loxfunction; -abstract interface LoxValue{ - 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{ +interface LoxCallable{ int arity(); - LoxValue call(Interpreter interpreter, LoxValue[] arguments); + TValue call(Interpreter interpreter, TValue[] arguments); } -class LoxStr : LoxValue{ - immutable string str; - alias str this; - mixin defaultCtor; - override string toString() const => str; +private struct Value{ + string str; + double dbl; + bool bln; + LoxCallable cal; + struct Nil{} + Nil nil; } -class LoxBool : LoxValue{ - immutable bool bln; - alias bln this; - mixin defaultCtor; - override string toString() const => bln.to!string; -} -class LoxNum : LoxValue{ - immutable double num; - alias num this; - mixin defaultCtor; - override string toString() const => num.to!string; - auto opBinary(string op)(const LoxNum rhs) const{ - mixin("auto v = num", op, "rhs.num;"); - static if(is(typeof(v) == bool)) - return new LoxBool(v); - else - return new LoxNum(v); +alias TValue = TaggedUnion!Value; +immutable tvalueNil = Value.Nil(); +string tvalueToString(TValue val){ + final switch(val.kind){ + case TValue.Kind.str: + return val.strValue; + case TValue.Kind.dbl: + return val.dblValue.to!string; + case TValue.Kind.bln: + return val.blnValue ? "true" : "false"; + case TValue.Kind.cal: + return ""; + case TValue.Kind.nil: + return "nil"; } } -class LoxNil : LoxValue{ - override string toString() const => ""; -} class Token{ TokenType type; string lexeme; - LoxValue literal; + TValue literal; int line; - this(TokenType type, string lexeme, LoxValue literal, int line) { + this(TokenType type, string lexeme, TValue literal, int line) { this.type = type; this.lexeme = lexeme; this.literal = literal; diff --git a/test/all.d b/test/all.d index a7c3542..5b79f0e 100755 --- a/test/all.d +++ b/test/all.d @@ -1,56 +1,22 @@ #!/bin/env rdmd -import std.stdio; import std.process; -import std.concurrency; import std.conv; -import std.string, std.format; -import std.algorithm, std.range; void main(){ - "./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/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/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; + 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; } - return r; -} -auto run(string file) => [ "./lox", file ].execute; -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)); + assert([ "./lox", "test/fib21.lox" ].execute.output == fib(6765)); + assert([ "./lox", "test/fib10.lox" ].execute.output == fib(34)); + assert([ "./lox", "test/closure.lox" ].execute.output == "1\n2\n"); } diff --git a/test/class.lox b/test/class.lox deleted file mode 100644 index 87bfb6a..0000000 --- a/test/class.lox +++ /dev/null @@ -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(); - diff --git a/test/err/already_defined.lox b/test/err/already_defined.lox deleted file mode 100644 index 6d142d4..0000000 --- a/test/err/already_defined.lox +++ /dev/null @@ -1,6 +0,0 @@ - -fun bad(){ - var a = "first"; - var a = "second"; -} - diff --git a/test/err/global_scope_return.lox b/test/err/global_scope_return.lox deleted file mode 100644 index c0658c2..0000000 --- a/test/err/global_scope_return.lox +++ /dev/null @@ -1,3 +0,0 @@ - -return ":)"; - diff --git a/test/err/invalid_syntax.lox b/test/err/invalid_syntax.lox deleted file mode 100644 index 24823b1..0000000 --- a/test/err/invalid_syntax.lox +++ /dev/null @@ -1,3 +0,0 @@ - -foo - diff --git a/test/err/self_ref_vardecl.lox b/test/err/self_ref_vardecl.lox deleted file mode 100644 index e58a127..0000000 --- a/test/err/self_ref_vardecl.lox +++ /dev/null @@ -1,3 +0,0 @@ - -var x = x + 1; - diff --git a/test/err/super_outside_class.lox b/test/err/super_outside_class.lox deleted file mode 100644 index 4a14239..0000000 --- a/test/err/super_outside_class.lox +++ /dev/null @@ -1,3 +0,0 @@ - -super.notEvenInAClass(); - diff --git a/test/err/super_without_superclass.lox b/test/err/super_without_superclass.lox deleted file mode 100644 index d6b4cdc..0000000 --- a/test/err/super_without_superclass.lox +++ /dev/null @@ -1,8 +0,0 @@ - -class Eclair{ - cook(){ - super.cook(); - print "Pipe full of crème pâtissière."; - } -} - diff --git a/test/err/undefined_var.lox b/test/err/undefined_var.lox deleted file mode 100644 index b3162ca..0000000 --- a/test/err/undefined_var.lox +++ /dev/null @@ -1,3 +0,0 @@ - -print hello; - diff --git a/test/fib_recursive.lox b/test/fib10.lox similarity index 100% rename from test/fib_recursive.lox rename to test/fib10.lox diff --git a/test/fib_for.lox b/test/fib21.lox similarity index 100% rename from test/fib_for.lox rename to test/fib21.lox diff --git a/test/fib_closure.lox b/test/fib_closure.lox deleted file mode 100644 index 42c8fe8..0000000 --- a/test/fib_closure.lox +++ /dev/null @@ -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(); - diff --git a/test/ops.lox b/test/ops.lox deleted file mode 100644 index 30da068..0000000 --- a/test/ops.lox +++ /dev/null @@ -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"; - diff --git a/test/scope.lox b/test/scope.lox deleted file mode 100644 index 4b9eadc..0000000 --- a/test/scope.lox +++ /dev/null @@ -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(); - diff --git a/test/shortcircuit.lox b/test/shortcircuit.lox deleted file mode 100644 index 7e03749..0000000 --- a/test/shortcircuit.lox +++ /dev/null @@ -1,12 +0,0 @@ - -fun scream(extra){ - extra = extra or "!"; - print "AAAA" + extra; -} - -print nil or true; - -scream(false); -scream(nil); -scream("?"); - diff --git a/test/super.lox b/test/super.lox deleted file mode 100644 index dec5f8d..0000000 --- a/test/super.lox +++ /dev/null @@ -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(); -