Compare commits
10 commits
a21c16d7e5
...
10c44eab2e
| Author | SHA1 | Date | |
|---|---|---|---|
| 10c44eab2e | |||
| 41404633da | |||
| 8fb449825d | |||
| aba643a88e | |||
| 7fa01b4fb9 | |||
| 848c846e09 | |||
| d8ac625429 | |||
| 52a7b73a9e | |||
| a1acefab0e | |||
| b0d934707b |
48 changed files with 1892 additions and 201 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,4 +16,5 @@ lox-test-*
|
||||||
*.lst
|
*.lst
|
||||||
|
|
||||||
.msc/
|
.msc/
|
||||||
|
test/test.lox
|
||||||
|
|
||||||
|
|
|
||||||
22
dub.sdl
22
dub.sdl
|
|
@ -4,13 +4,19 @@ 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 "taggedalgebraic" version="~>0.11.23"
|
dependency "colored" version="~>0.0.33"
|
||||||
targetType "executable"
|
targetType "executable"
|
||||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
sourcePaths
|
||||||
|
configuration "clox" {
|
||||||
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs"
|
/* debugVersions "traceExec" */
|
||||||
|
debugVersions "printCode"
|
||||||
configuration "jlox" {
|
targetType "executable"
|
||||||
sourcePaths "src/jlox" "src/common"
|
sourcePaths "src/clox" "src/common"
|
||||||
|
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||||
|
}
|
||||||
|
configuration "jlox" {
|
||||||
|
targetType "executable"
|
||||||
|
sourcePaths "src/jlox" "src/common"
|
||||||
|
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
|
||||||
|
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
src/clox/chunk.d
Normal file
69
src/clox/chunk.d
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
30
src/clox/compiler.d
Normal file
30
src/clox/compiler.d
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
26
src/clox/container/int24.d
Normal file
26
src/clox/container/int24.d
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
62
src/clox/container/rle.d
Normal file
62
src/clox/container/rle.d
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
26
src/clox/container/stack.d
Normal file
26
src/clox/container/stack.d
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
73
src/clox/container/varint.d
Normal file
73
src/clox/container/varint.d
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
59
src/clox/container/vartype.d
Normal file
59
src/clox/container/vartype.d
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
66
src/clox/dbg.d
Normal file
66
src/clox/dbg.d
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
51
src/clox/emitter.d
Normal file
51
src/clox/emitter.d
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
46
src/clox/main.d
Normal file
46
src/clox/main.d
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
66
src/clox/parser.d
Normal file
66
src/clox/parser.d
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/clox/parserules.d
Normal file
128
src/clox/parserules.d
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
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),
|
||||||
|
];
|
||||||
|
|
||||||
157
src/clox/scanner.d
Normal file
157
src/clox/scanner.d
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
src/clox/util.d
Normal file
6
src/clox/util.d
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module clox.util;
|
||||||
|
|
||||||
|
import std.stdio;
|
||||||
|
import std.traits : isUnsigned;
|
||||||
|
import std.container.array;
|
||||||
|
|
||||||
53
src/clox/value.d
Normal file
53
src/clox/value.d
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
114
src/clox/vm.d
Normal file
114
src/clox/vm.d
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -6,3 +6,39 @@ 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,40 @@ import common.util;
|
||||||
|
|
||||||
class Environment{
|
class Environment{
|
||||||
Environment enclosing;
|
Environment enclosing;
|
||||||
private TValue[string] values;
|
private LoxValue[string] values;
|
||||||
|
|
||||||
mixin defaultCtor;
|
mixin defaultCtor;
|
||||||
|
|
||||||
void define(string name, TValue value){
|
void define(string name, LoxValue value){
|
||||||
values[name] = value;
|
values[name] = value;
|
||||||
}
|
}
|
||||||
TValue get(Token name){
|
LoxValue get(Token name){
|
||||||
if(TValue* value = name.lexeme in values)
|
if(LoxValue* 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 ~ "'.");
|
||||||
}
|
}
|
||||||
void assign(Token name, TValue value){
|
Environment ancestor(uint distance){
|
||||||
if(name.lexeme in values)
|
Environment environment = this;
|
||||||
return values[name.lexeme] = value;
|
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;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ 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;
|
||||||
|
|
@ -15,13 +13,17 @@ 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, TValue);
|
private alias rTypes = AliasSeq!(string, LoxValue, void);
|
||||||
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(){
|
||||||
|
|
@ -47,12 +49,17 @@ 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){
|
||||||
TValue value;
|
LoxValue value;
|
||||||
mixin defCtorAndAccept;
|
mixin defCtorAndAccept;
|
||||||
}
|
}
|
||||||
static class Logical : typeof(this){
|
static class Logical : typeof(this){
|
||||||
|
|
@ -61,6 +68,21 @@ 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;
|
||||||
|
|
@ -72,31 +94,3 @@ 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); */
|
|
||||||
/* } */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
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;
|
||||||
|
|
@ -11,10 +12,12 @@ import jlox.expr;
|
||||||
import jlox.stmt;
|
import jlox.stmt;
|
||||||
import jlox.token;
|
import jlox.token;
|
||||||
import jlox.tokentype;
|
import jlox.tokentype;
|
||||||
import jlox.token : TValue;
|
import jlox.token : LoxValue;
|
||||||
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;
|
||||||
|
|
@ -24,14 +27,14 @@ class RuntimeError : Exception{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Return : Exception{
|
class Return : Exception{
|
||||||
const TValue value;
|
const LoxValue value;
|
||||||
this(TValue value){
|
this(LoxValue value){
|
||||||
super(null);
|
super(null);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
|
||||||
Environment globals = new Environment();
|
Environment globals = new Environment();
|
||||||
private Environment environment;
|
private Environment environment;
|
||||||
this(){
|
this(){
|
||||||
|
|
@ -39,20 +42,24 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
||||||
|
|
||||||
import std.datetime.stopwatch;
|
import std.datetime.stopwatch;
|
||||||
auto sw = StopWatch(AutoStart.yes);
|
auto sw = StopWatch(AutoStart.yes);
|
||||||
globals.define("clock", TValue.cal(new class LoxCallable{
|
globals.define("clock", new class LoxCallable{
|
||||||
int arity() => 0;
|
int arity() => 0;
|
||||||
TValue call(Interpreter interpreter, TValue[] arguments) => TValue.dbl(sw.peek.total!"usecs" / (1000.0 * 1000.0));
|
LoxValue call(Interpreter interpreter, LoxValue[] arguments) => new LoxNum(sw.peek.total!"usecs" / (1000.0 * 1000.0));
|
||||||
}));
|
override string toString() const => "<native clock>";
|
||||||
|
});
|
||||||
|
|
||||||
version(LoxExtraNativeFuncs){
|
version(LoxExtraNativeFuncs){
|
||||||
globals.define("sleep", TValue.cal(new class LoxCallable{
|
globals.define("sleep", new class LoxCallable{
|
||||||
int arity() => 1;
|
int arity() => 1;
|
||||||
import core.thread.osthread;
|
import core.thread.osthread;
|
||||||
TValue call(Interpreter interpreter, TValue[] arguments){
|
LoxValue call(Interpreter interpreter, LoxValue[] arguments){
|
||||||
Thread.sleep(dur!"usecs"(cast(long)(arguments[0].dblValue * 1000 * 1000)));
|
if(cast(LoxNum)arguments[0] is null)
|
||||||
return TValue.nil(tvalueNil);
|
throw new RuntimeError(null, "Expected number to sleep/1");
|
||||||
|
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){
|
||||||
|
|
@ -76,38 +83,59 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
||||||
this.environment = previous;
|
this.environment = previous;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private TValue evaluate(Expr expr){
|
private LoxValue evaluate(Expr expr){
|
||||||
return expr.accept(this);
|
return expr.accept(this);
|
||||||
}
|
}
|
||||||
private bool isTruthy(TValue val){
|
private bool isTruthy(LoxValue val){
|
||||||
switch(val.kind){
|
if(cast(LoxNil)val)
|
||||||
case TValue.Kind.nil:
|
|
||||||
return false;
|
return false;
|
||||||
case TValue.Kind.bln:
|
if(auto b = cast(LoxBool)val)
|
||||||
return val.blnValue;
|
return b;
|
||||||
default:
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
private bool isEqual(LoxValue a, LoxValue b){
|
||||||
|
return a.equals(b);
|
||||||
}
|
}
|
||||||
private bool isEqual(TValue a, TValue b){
|
private void checkNumberOperand(Token operator, LoxValue operand){
|
||||||
return a == b;
|
if(cast(LoxNum)operand)
|
||||||
}
|
|
||||||
private void checkNumberOperand(Token operator, TValue 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);
|
LoxFunction func = new LoxFunction(stmt, environment, false);
|
||||||
environment.define(stmt.name.lexeme, TValue.cal(func));
|
environment.define(stmt.name.lexeme, func);
|
||||||
}
|
}
|
||||||
void visit(Stmt.If stmt){
|
void visit(Stmt.If stmt){
|
||||||
if(isTruthy(evaluate(stmt.condition)))
|
if(isTruthy(evaluate(stmt.condition)))
|
||||||
|
|
@ -116,41 +144,45 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
||||||
execute(stmt.elseBranch);
|
execute(stmt.elseBranch);
|
||||||
}
|
}
|
||||||
void visit(Stmt.Print stmt){
|
void visit(Stmt.Print stmt){
|
||||||
TValue value = evaluate(stmt.expression);
|
version(LoxPrintMultiple){
|
||||||
writeln(tvalueToString(value));
|
writeln(stmt.expressions.map!(x => evaluate(x)).map!(x => x.toString).join('\t'));
|
||||||
|
} else {
|
||||||
|
LoxValue value = evaluate(stmt.expression);
|
||||||
|
writeln(value.toString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void visit(Stmt.Return stmt){
|
void visit(Stmt.Return stmt){
|
||||||
TValue value = stmt.value !is null ? evaluate(stmt.value) : TValue.nil(tvalueNil);
|
LoxValue value = stmt.value !is null ? evaluate(stmt.value) : new LoxNil();
|
||||||
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 ? TValue.nil(tvalueNil) : evaluate(stmt.initialiser));
|
environment.define(stmt.name.lexeme, stmt.initialiser is null ? new LoxNil() : 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
TValue visit(Expr.Literal expr){
|
LoxValue visit(Expr.Literal expr){
|
||||||
return expr.value;
|
return expr.value;
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Grouping expr){
|
LoxValue visit(Expr.Grouping expr){
|
||||||
return evaluate(expr.expression);
|
return evaluate(expr.expression);
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Unary expr){
|
LoxValue visit(Expr.Unary expr){
|
||||||
TValue right = evaluate(expr.right);
|
LoxValue 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 TValue.dbl(-right.dblValue);
|
return new LoxNum(-cast(LoxNum)right);
|
||||||
case TokenType.BANG:
|
case TokenType.BANG:
|
||||||
return TValue.bln(!isTruthy(right));
|
return new LoxBool(!isTruthy(right));
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Logical expr){
|
LoxValue visit(Expr.Logical expr){
|
||||||
TValue left = evaluate(expr.left);
|
LoxValue 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;
|
||||||
|
|
@ -160,59 +192,84 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
||||||
}
|
}
|
||||||
return evaluate(expr.right);
|
return evaluate(expr.right);
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Binary expr){
|
LoxValue visit(Expr.Set expr){
|
||||||
TValue left = evaluate(expr.left);
|
LoxValue object = evaluate(expr.object);
|
||||||
TValue right = evaluate(expr.right);
|
if(!(cast(LoxInstance)object))
|
||||||
static string m(TokenType t, string op, string v, string vv){
|
throw new RuntimeError(expr.name, "Only instances have fields.");
|
||||||
return q{case %s:
|
LoxValue value = evaluate(expr.value);
|
||||||
checkNumberOperand(expr.operator, left);
|
(cast(LoxInstance)object).set(expr.name, value);
|
||||||
checkNumberOperand(expr.operator, right);
|
return value;
|
||||||
return TValue.%s( left.%s %s right.%s );
|
|
||||||
}.format(t, v, vv, op, vv);
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
with(TokenType) switch(expr.operator.type){
|
with(TokenType) switch(expr.operator.type){
|
||||||
static foreach(t, op; [ MINUS: "-", SLASH: "/", STAR: "*" ])
|
static foreach(t, op; [ PLUS: "+", MINUS: "-", SLASH: "/", STAR: "*", GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ]){
|
||||||
mixin(ctEval!(m(t, op, "dbl", "dblValue")));
|
case t:
|
||||||
|
static if(t == PLUS){
|
||||||
case PLUS:
|
if(cast(LoxStr)left && cast(LoxStr)right)
|
||||||
if(left.isDbl && right.isDbl)
|
return new LoxStr(cast(LoxStr)left ~ cast(LoxStr)right);
|
||||||
return TValue.dbl(left.dblValue + right.dblValue);
|
|
||||||
else if(left.isStr && right.isStr)
|
|
||||||
return TValue.str(left.strValue ~ right.strValue);
|
|
||||||
version(LoxConcatNonStrings){
|
version(LoxConcatNonStrings){
|
||||||
if(left.isStr || right.isStr)
|
if(cast(LoxStr)left || cast(LoxStr)right)
|
||||||
return TValue.str(tvalueToString(left) ~ tvalueToString(right));
|
return new LoxStr(left.toString ~ right.toString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
checkNumberOperand(expr.operator, left);
|
checkNumberOperand(expr.operator, left);
|
||||||
checkNumberOperand(expr.operator, right);
|
checkNumberOperand(expr.operator, right);
|
||||||
|
return (cast(LoxNum)left).opBinary!op(cast(LoxNum)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 TValue.bln(!isEqual(left, right));
|
return new LoxBool(!isEqual(left, right));
|
||||||
case EQUAL_EQUAL:
|
case EQUAL_EQUAL:
|
||||||
return TValue.bln(isEqual(left, right));
|
return new LoxBool(isEqual(left, right));
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Call expr){
|
LoxValue visit(Expr.Call expr){
|
||||||
TValue callee = evaluate(expr.callee);
|
LoxValue callee = evaluate(expr.callee);
|
||||||
if(!callee.isCal)
|
if(!cast(LoxCallable)callee)
|
||||||
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 = callee.calValue;
|
LoxCallable func = cast(LoxCallable)callee;
|
||||||
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);
|
||||||
}
|
}
|
||||||
TValue visit(Expr.Variable expr){
|
LoxValue visit(Expr.Get expr){
|
||||||
return environment.get(expr.name);
|
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.Assign expr){
|
LoxValue visit(Expr.Variable expr){
|
||||||
TValue value = evaluate(expr.value);
|
return lookUpVariable(expr.name, expr);
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
src/jlox/loxclass.d
Normal file
37
src/jlox/loxclass.d
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,31 +2,44 @@ 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 common.util;
|
import jlox.loxinstance;
|
||||||
|
|
||||||
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;
|
||||||
TValue call(Interpreter interpreter, TValue[] arguments){
|
LoxValue call(Interpreter interpreter, LoxValue[] 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){
|
||||||
return returnValue.value;
|
if(isInitialiser)
|
||||||
|
return closure.getAt(0, "this");
|
||||||
|
return cast(LoxValue)returnValue.value;
|
||||||
}
|
}
|
||||||
return TValue.nil(tvalueNil);
|
if(isInitialiser)
|
||||||
|
return closure.getAt(0, "this");
|
||||||
|
return new LoxNil();
|
||||||
}
|
}
|
||||||
|
override string toString() const => "function";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
27
src/jlox/loxinstance.d
Normal file
27
src/jlox/loxinstance.d
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -14,6 +14,11 @@ 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(){
|
||||||
|
|
@ -42,7 +47,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +60,11 @@ 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;
|
||||||
|
|
||||||
|
|
@ -77,6 +89,8 @@ 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)
|
||||||
|
|
@ -85,17 +99,21 @@ 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 3;
|
return RetVal.other;
|
||||||
}
|
}
|
||||||
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();
|
||||||
} catch(MainException rte){
|
else
|
||||||
return Lox.hadError ? 1 : 2;
|
Lox.runFile("/dev/stdin");
|
||||||
}
|
}
|
||||||
return 0;
|
} catch(MainException rte){
|
||||||
|
return Lox.hadError ? RetVal.other : RetVal.runtime;
|
||||||
|
}
|
||||||
|
return RetVal.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,19 @@ class Parser{
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stmt printStatement(){
|
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();
|
Expr value = expression();
|
||||||
consume(TokenType.SEMICOLON, "Expect ';' after value.");
|
consume(TokenType.SEMICOLON, "Expect ';' after value.");
|
||||||
return new Stmt.Print(value);
|
return new Stmt.Print(value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private Stmt returnStatement(){
|
private Stmt returnStatement(){
|
||||||
Token keyword = previous();
|
Token keyword = previous();
|
||||||
Expr value;
|
Expr value;
|
||||||
|
|
@ -135,7 +144,7 @@ class Parser{
|
||||||
else
|
else
|
||||||
initialiser = expressionStatement();
|
initialiser = expressionStatement();
|
||||||
|
|
||||||
Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(TValue.bln(true)) : expression();
|
Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(new LoxBool(true)) : expression();
|
||||||
consume(TokenType.SEMICOLON, "Expect ';' after loop condition.");
|
consume(TokenType.SEMICOLON, "Expect ';' after loop condition.");
|
||||||
|
|
||||||
Expr increment;
|
Expr increment;
|
||||||
|
|
@ -188,8 +197,11 @@ 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;
|
||||||
|
|
@ -271,29 +283,41 @@ 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
|
} else if(match(TokenType.DOT)){
|
||||||
|
Token name = consume(TokenType.IDENTIFIER, "Expect property name after '.'.");
|
||||||
|
expr = new Expr.Get(expr, name);
|
||||||
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
private Expr primary(){
|
private Expr primary(){
|
||||||
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(TValue.bln(false));
|
return new Expr.Literal(new LoxBool(false));
|
||||||
if(match(TokenType.TRUE))
|
if(match(TokenType.TRUE))
|
||||||
return new Expr.Literal(TValue.bln(true));
|
return new Expr.Literal(new LoxBool(true));
|
||||||
if(match(TokenType.NIL))
|
if(match(TokenType.NIL))
|
||||||
return new Expr.Literal(tvalueNil);
|
return new Expr.Literal(new LoxNil());
|
||||||
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(){
|
||||||
|
|
@ -304,12 +328,28 @@ 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.FUN))
|
|
||||||
return fun("function");
|
|
||||||
if(match(TokenType.VAR))
|
if(match(TokenType.VAR))
|
||||||
return varDeclaration();
|
return varDeclaration();
|
||||||
|
if(match(TokenType.FUN))
|
||||||
|
return fun("function");
|
||||||
|
if(match(TokenType.CLASS))
|
||||||
|
return classDeclaration();
|
||||||
return statement();
|
return statement();
|
||||||
} catch(ParseError error){
|
} catch(ParseError error){
|
||||||
synchronise();
|
synchronise();
|
||||||
|
|
|
||||||
198
src/jlox/resolver.d
Normal file
198
src/jlox/resolver.d
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -6,11 +6,9 @@ import std.conv;
|
||||||
import jlox.token;
|
import jlox.token;
|
||||||
import jlox.tokentype;
|
import jlox.tokentype;
|
||||||
import jlox.main;
|
import jlox.main;
|
||||||
|
import common.util;
|
||||||
|
|
||||||
private bool isAlpha_(dchar c) => c.isAlpha || c == '_';
|
class Scanner{
|
||||||
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;
|
||||||
|
|
@ -30,7 +28,7 @@ class Scanner {
|
||||||
current++;
|
current++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
private void addToken(TokenType type, TValue literal = TValue.nil(tvalueNil)){
|
private void addToken(TokenType type, LoxValue literal = new LoxNil()){
|
||||||
string text = source[start .. current];
|
string text = source[start .. current];
|
||||||
tokens ~= new Token(type, text, literal, line);
|
tokens ~= new Token(type, text, literal, line);
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +52,7 @@ class Scanner {
|
||||||
}
|
}
|
||||||
advance();
|
advance();
|
||||||
string value = source[start + 1 .. current -1];
|
string value = source[start + 1 .. current -1];
|
||||||
addToken(TokenType.STRING, TValue.str(value));
|
addToken(TokenType.STRING, new LoxStr(value));
|
||||||
}
|
}
|
||||||
private void number(){
|
private void number(){
|
||||||
while(peek().isDigit)
|
while(peek().isDigit)
|
||||||
|
|
@ -64,7 +62,7 @@ class Scanner {
|
||||||
while(peek().isDigit)
|
while(peek().isDigit)
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
addToken(TokenType.NUMBER, TValue.dbl(source[start .. current].to!double));
|
addToken(TokenType.NUMBER, new LoxNum(source[start .. current].to!double));
|
||||||
}
|
}
|
||||||
private TokenType keywords(string word){
|
private TokenType keywords(string word){
|
||||||
with(TokenType) switch(word){
|
with(TokenType) switch(word){
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ 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);
|
||||||
|
|
@ -33,6 +34,12 @@ 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;
|
||||||
|
|
@ -50,6 +57,9 @@ abstract class Stmt{
|
||||||
mixin defCtorAndAccept;
|
mixin defCtorAndAccept;
|
||||||
}
|
}
|
||||||
static class Print : typeof(this){
|
static class Print : typeof(this){
|
||||||
|
version(LoxPrintMultiple)
|
||||||
|
Expr[] expressions;
|
||||||
|
else
|
||||||
Expr expression;
|
Expr expression;
|
||||||
mixin defCtorAndAccept;
|
mixin defCtorAndAccept;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,70 @@
|
||||||
module jlox.token;
|
module jlox.token;
|
||||||
|
|
||||||
|
import std.stdio;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
|
import std.format : format;
|
||||||
|
|
||||||
import taggedalgebraic;
|
import common.util;
|
||||||
|
|
||||||
import jlox.tokentype;
|
import jlox.tokentype;
|
||||||
import jlox.interpreter;
|
import jlox.interpreter;
|
||||||
|
import jlox.loxclass;
|
||||||
|
import jlox.loxinstance;
|
||||||
|
import jlox.loxfunction;
|
||||||
|
|
||||||
interface LoxCallable{
|
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{
|
||||||
int arity();
|
int arity();
|
||||||
TValue call(Interpreter interpreter, TValue[] arguments);
|
LoxValue call(Interpreter interpreter, LoxValue[] arguments);
|
||||||
}
|
}
|
||||||
private struct Value{
|
class LoxStr : LoxValue{
|
||||||
string str;
|
immutable string str;
|
||||||
double dbl;
|
alias str this;
|
||||||
bool bln;
|
mixin defaultCtor;
|
||||||
LoxCallable cal;
|
override string toString() const => str;
|
||||||
struct Nil{}
|
|
||||||
Nil nil;
|
|
||||||
}
|
}
|
||||||
alias TValue = TaggedUnion!Value;
|
class LoxBool : LoxValue{
|
||||||
immutable tvalueNil = Value.Nil();
|
immutable bool bln;
|
||||||
string tvalueToString(TValue val){
|
alias bln this;
|
||||||
final switch(val.kind){
|
mixin defaultCtor;
|
||||||
case TValue.Kind.str:
|
override string toString() const => bln.to!string;
|
||||||
return val.strValue;
|
}
|
||||||
case TValue.Kind.dbl:
|
class LoxNum : LoxValue{
|
||||||
return val.dblValue.to!string;
|
immutable double num;
|
||||||
case TValue.Kind.bln:
|
alias num this;
|
||||||
return val.blnValue ? "true" : "false";
|
mixin defaultCtor;
|
||||||
case TValue.Kind.cal:
|
override string toString() const => num.to!string;
|
||||||
return "<function>";
|
auto opBinary(string op)(const LoxNum rhs) const{
|
||||||
case TValue.Kind.nil:
|
mixin("auto v = num", op, "rhs.num;");
|
||||||
return "nil";
|
static if(is(typeof(v) == bool))
|
||||||
|
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;
|
||||||
TValue literal;
|
LoxValue literal;
|
||||||
int line;
|
int line;
|
||||||
|
|
||||||
this(TokenType type, string lexeme, TValue literal, int line) {
|
this(TokenType type, string lexeme, LoxValue literal, int line) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.lexeme = lexeme;
|
this.lexeme = lexeme;
|
||||||
this.literal = literal;
|
this.literal = literal;
|
||||||
|
|
|
||||||
44
test/all.d
44
test/all.d
|
|
@ -1,10 +1,37 @@
|
||||||
#!/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(){
|
||||||
string fib(uint n){
|
"./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n");
|
||||||
|
"./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n");
|
||||||
|
"./test/closure.lox".match("1\n2\n");
|
||||||
|
"./test/scope.lox".match("global first first second first ".replace(' ', '\n').repeat(2).join("\n"));
|
||||||
|
"./test/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 = "";
|
string r = "";
|
||||||
double a = 0;
|
double a = 0;
|
||||||
double temp;
|
double temp;
|
||||||
|
|
@ -14,9 +41,16 @@ void main(){
|
||||||
a = b;
|
a = b;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
assert([ "./lox", "test/fib21.lox" ].execute.output == fib(6765));
|
auto run(string file) => [ "./lox", file ].execute;
|
||||||
assert([ "./lox", "test/fib10.lox" ].execute.output == fib(34));
|
void match(string file, string correct){
|
||||||
assert([ "./lox", "test/closure.lox" ].execute.output == "1\n2\n");
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
test/class.lox
Normal file
21
test/class.lox
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
6
test/err/already_defined.lox
Normal file
6
test/err/already_defined.lox
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
fun bad(){
|
||||||
|
var a = "first";
|
||||||
|
var a = "second";
|
||||||
|
}
|
||||||
|
|
||||||
3
test/err/global_scope_return.lox
Normal file
3
test/err/global_scope_return.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
return ":)";
|
||||||
|
|
||||||
3
test/err/invalid_syntax.lox
Normal file
3
test/err/invalid_syntax.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
foo
|
||||||
|
|
||||||
3
test/err/self_ref_vardecl.lox
Normal file
3
test/err/self_ref_vardecl.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
var x = x + 1;
|
||||||
|
|
||||||
3
test/err/super_outside_class.lox
Normal file
3
test/err/super_outside_class.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
super.notEvenInAClass();
|
||||||
|
|
||||||
8
test/err/super_without_superclass.lox
Normal file
8
test/err/super_without_superclass.lox
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
class Eclair{
|
||||||
|
cook(){
|
||||||
|
super.cook();
|
||||||
|
print "Pipe full of crème pâtissière.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
test/err/undefined_var.lox
Normal file
3
test/err/undefined_var.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
print hello;
|
||||||
|
|
||||||
18
test/fib_closure.lox
Normal file
18
test/fib_closure.lox
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
17
test/ops.lox
Normal file
17
test/ops.lox
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
19
test/scope.lox
Normal file
19
test/scope.lox
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
var a = "global";
|
||||||
|
|
||||||
|
fun f(){
|
||||||
|
print a;
|
||||||
|
var a = "first";
|
||||||
|
print a;
|
||||||
|
{
|
||||||
|
print a;
|
||||||
|
var a = "second";
|
||||||
|
print a;
|
||||||
|
}
|
||||||
|
print a;
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
||||||
|
print "";
|
||||||
|
f();
|
||||||
|
|
||||||
12
test/shortcircuit.lox
Normal file
12
test/shortcircuit.lox
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
fun scream(extra){
|
||||||
|
extra = extra or "!";
|
||||||
|
print "AAAA" + extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
print nil or true;
|
||||||
|
|
||||||
|
scream(false);
|
||||||
|
scream(nil);
|
||||||
|
scream("?");
|
||||||
|
|
||||||
36
test/super.lox
Normal file
36
test/super.lox
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue