Jumping Back and Forth 23
This commit is contained in:
parent
98e7f950cf
commit
8717d37445
13 changed files with 299 additions and 30 deletions
6
dub.sdl
6
dub.sdl
|
|
@ -9,10 +9,10 @@ targetType "executable"
|
|||
sourcePaths
|
||||
|
||||
configuration "clox" {
|
||||
/* buildOptions "betterC" */
|
||||
debugVersions "memTrace"
|
||||
buildOptions "betterC"
|
||||
/* debugVersions "memTrace" */
|
||||
/* debugVersions "traceExec" */
|
||||
/* debugVersions "printCode" */
|
||||
debugVersions "printCode"
|
||||
dflags "-checkaction=C"
|
||||
libs "libbacktrace"
|
||||
targetType "executable"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ enum OpCode : ubyte{
|
|||
@(OpColour("060", "200", "150")) SetLocal,
|
||||
@(OpColour("000", "200", "150")) SetGlobal,
|
||||
|
||||
@(OpColour("000", "255", "000")) Jump,
|
||||
@(OpColour("000", "200", "000")) JumpIfFalse,
|
||||
@(OpColour("010", "255", "000")) Loop,
|
||||
|
||||
@(OpColour("255", "100", "100")) Equal,
|
||||
@(OpColour("255", "100", "100")) Greater,
|
||||
@(OpColour("255", "100", "100")) Less,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ struct DynArray(T){
|
|||
assert(ptr || !count);
|
||||
return ptr[0 .. count];
|
||||
}
|
||||
auto opSlice(size_t i, size_t o){
|
||||
assert(ptr || !count);
|
||||
return ptr[i .. o];
|
||||
}
|
||||
ref auto opIndex(size_t i){
|
||||
assert(ptr && count);
|
||||
return ptr[i];
|
||||
|
|
|
|||
|
|
@ -25,9 +25,32 @@ private long constantInstruction(alias OpCode op)(const char* name, Chunk* chunk
|
|||
printf(" ");
|
||||
return offset + 1 + constant.len;
|
||||
}
|
||||
private long jumpInstruction(alias OpCode op)(const char* name, int sign, Chunk* chunk, long offset){
|
||||
ushort jump = cast(ushort)(chunk.code[offset + 1] << 8) | chunk.code[offset + 2];
|
||||
enum c = getUDAs!(op, OpColour)[0];
|
||||
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4ld ", Colour.Green)).ptr, name, (jump * sign) + offset + 3);
|
||||
printf("%i ", sign * jump);
|
||||
return offset + 3;
|
||||
}
|
||||
private long stackIndexInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
|
||||
VarUint index = VarUint.read(&chunk.code[offset + 1]);
|
||||
enum c = getUDAs!(op, OpColour)[0];
|
||||
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Green)).ptr, name, index.i);
|
||||
printf(" ");
|
||||
return offset + 1 + index.len;
|
||||
}
|
||||
|
||||
void disassembleChunk(Chunk* chunk, const char* name = "chunk"){
|
||||
printf(" == %s ==\n", name);
|
||||
void printHeader(Chunk* chunk, const char* name = "execution"){
|
||||
printf(" ============== %s ==============\n", name);
|
||||
}
|
||||
void disassembleChunk(Chunk* chunk, const char* name = "disassembly"){
|
||||
import std.range : chunks;
|
||||
printHeader(chunk, name);
|
||||
if(0) foreach(bb; chunk.code[].chunks(32)){
|
||||
foreach(b; bb)
|
||||
printf("%02x ", b);
|
||||
printf("\n");
|
||||
}
|
||||
for(long offset = 0; offset < chunk.code.count;){
|
||||
offset = disassembleInstruction(chunk, offset);
|
||||
printf("\n");
|
||||
|
|
@ -41,14 +64,18 @@ long disassembleInstruction(Chunk* chunk, long offset){
|
|||
} else {
|
||||
printf(colour!("%4d ", Colour.Black).ptr, chunk.lines[offset]);
|
||||
}
|
||||
ubyte instruction = chunk.code[offset];
|
||||
OpCode instruction = cast(OpCode)chunk.code[offset];
|
||||
printf(colour!(" %02x ", Colour.Black).ptr, instruction);
|
||||
switch(instruction){
|
||||
static foreach(e; EnumMembers!OpCode){
|
||||
static if(e == OpCode.Constant || e == OpCode.DefineGlobal || e == OpCode.GetGlobal){
|
||||
static if(e == OpCode.Constant || e == OpCode.DefineGlobal || e == OpCode.GetGlobal || e == OpCode.SetGlobal){
|
||||
case e: return constantInstruction!(e)(e.stringof, chunk, offset);
|
||||
} else static if(e == OpCode.GetLocal || e == OpCode.SetLocal){
|
||||
case e: return stackIndexInstruction!e(e.stringof, chunk, offset);
|
||||
} else static if(e == OpCode.Jump || e == OpCode.JumpIfFalse || e == OpCode.Loop){
|
||||
case e: return jumpInstruction!e(e.stringof, e == OpCode.Loop ? -1 : 1, chunk, offset);
|
||||
} else {
|
||||
case e:
|
||||
return simpleInstruction!e(e.stringof, offset);
|
||||
case e: return simpleInstruction!e(e.stringof, offset);
|
||||
}
|
||||
}
|
||||
default: printf("(Unknown opcode %d)", instruction);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import clox.container.varint;
|
|||
|
||||
struct Emitter{
|
||||
Compiler* compiler;
|
||||
private uint line = 1;
|
||||
uint setLine(uint line) => this.line = line;
|
||||
void initialise(Compiler* compiler){
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ struct Obj{
|
|||
static if(type == Type.None) return &this;
|
||||
}
|
||||
|
||||
int print() const{
|
||||
int print(const char* strFmt = `"%s"`) const{
|
||||
final switch(type){
|
||||
case Type.String: return printf(`"%s"`, asString.chars.ptr); break;
|
||||
case Type.String: return printf(strFmt, asString.chars.ptr); break;
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,5 +95,14 @@ struct Obj{
|
|||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
version(D_BetterC) {} else {
|
||||
static string toString(Obj* obj){
|
||||
final switch(obj.type){
|
||||
case Type.String: return '"' ~ obj.asString.chars.idup ~ '"';
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,10 +60,11 @@ struct Parser{
|
|||
}
|
||||
void varDeclaration(){
|
||||
long global = parseVariable("Expect variable name.");
|
||||
if(match(Token.Type.Equal))
|
||||
if(match(Token.Type.Equal)){
|
||||
expression();
|
||||
else
|
||||
} else {
|
||||
compiler.emitter.emit(OpCode.Nil);
|
||||
}
|
||||
consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
|
||||
defineVariable(global);
|
||||
}
|
||||
|
|
@ -78,6 +79,12 @@ struct Parser{
|
|||
void statement(){
|
||||
if(match(Token.Type.Print)){
|
||||
printStatement();
|
||||
} else if(match(Token.Type.If)){
|
||||
ifStatement();
|
||||
} else if(match(Token.Type.While)){
|
||||
whileStatement();
|
||||
} else if(match(Token.Type.For)){
|
||||
forStatement();
|
||||
} else if(match(Token.Type.LeftBrace)){
|
||||
beginScope();
|
||||
block();
|
||||
|
|
@ -90,12 +97,107 @@ struct Parser{
|
|||
consume(Token.Type.Semicolon, "Expect ';' after value.");
|
||||
compiler.emitter.emit(OpCode.Print);
|
||||
}
|
||||
void ifStatement(){
|
||||
consume(Token.Type.LeftParen, "Expect '(' after 'if'.");
|
||||
expression();
|
||||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||
|
||||
int thenJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
statement();
|
||||
|
||||
int elseJump = emitJump(OpCode.Jump);
|
||||
|
||||
patchJump(thenJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
|
||||
if(match(Token.Type.Else))
|
||||
statement();
|
||||
|
||||
patchJump(elseJump);
|
||||
}
|
||||
void whileStatement(){
|
||||
int loopStart = cast(int)compiler.currentChunk.code.count;
|
||||
consume(Token.Type.LeftParen, "Expect '(' after 'while'.");
|
||||
expression();
|
||||
consume(Token.Type.RightParen, "Expect ')' after condition.");
|
||||
|
||||
int exitJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
statement();
|
||||
emitLoop(loopStart);
|
||||
|
||||
patchJump(exitJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
}
|
||||
void forStatement(){
|
||||
beginScope();
|
||||
consume(Token.Type.LeftParen, "Expect '(' after 'for'.");
|
||||
if(match(Token.Type.Semicolon)){
|
||||
// No initialiser.
|
||||
} else if(match(Token.Type.Var)){
|
||||
varDeclaration();
|
||||
} else {
|
||||
expressionStatement();
|
||||
}
|
||||
|
||||
int loopStart = cast(int)compiler.currentChunk.code.count;
|
||||
int exitJump = -1;
|
||||
if(!match(Token.Type.Semicolon)){
|
||||
expression();
|
||||
consume(Token.Type.Semicolon, "Expect ';' after loop condition.");
|
||||
exitJump = emitJump(OpCode.JumpIfFalse);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
}
|
||||
|
||||
if(!match(Token.Type.RightParen)){
|
||||
int bodyJump = emitJump(OpCode.Jump);
|
||||
int incrementStart = cast(int)compiler.currentChunk.code.count;
|
||||
expression();
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
consume(Token.Type.RightParen, "Expect ')' after for clauses.");
|
||||
emitLoop(loopStart);
|
||||
loopStart = incrementStart;
|
||||
patchJump(bodyJump);
|
||||
}
|
||||
|
||||
statement();
|
||||
emitLoop(loopStart);
|
||||
|
||||
if(exitJump != -1){
|
||||
patchJump(exitJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
}
|
||||
|
||||
endScope();
|
||||
}
|
||||
void block(){
|
||||
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF))
|
||||
declaration();
|
||||
consume(Token.Type.RightBrace, "Expect '}' after block.");
|
||||
}
|
||||
|
||||
void patchJump(int offset){
|
||||
long jump = compiler.currentChunk.code.count - offset - 2;
|
||||
if(jump > ushort.max)
|
||||
error("Too much code to jump over.");
|
||||
compiler.currentChunk.code[offset] = (jump >> 8) & 0xff;
|
||||
compiler.currentChunk.code[offset + 1] = jump & 0xff;
|
||||
}
|
||||
int emitJump(OpCode instruction){
|
||||
compiler.emitter.emit(instruction, ubyte(0xff), ubyte(0xff));
|
||||
return cast(int)compiler.currentChunk.code.count - 2;
|
||||
}
|
||||
void emitLoop(int loopStart){
|
||||
compiler.emitter.emit(OpCode.Loop);
|
||||
|
||||
int offset = cast(int)compiler.currentChunk.code.count - loopStart + 2;
|
||||
if(offset > ushort.max)
|
||||
error("Loop body too large.");
|
||||
|
||||
compiler.emitter.emit(cast(ubyte)((offset >> 8) & 0xff));
|
||||
compiler.emitter.emit(cast(ubyte)(offset & 0xff));
|
||||
}
|
||||
void defineVariable(long global){
|
||||
if(compiler.scopeDepth > 0){
|
||||
markInitialised();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ private void grouping(Compiler* compiler, bool _){
|
|||
private void unary(Compiler* compiler, bool _){
|
||||
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;
|
||||
|
|
@ -28,7 +27,6 @@ private void binary(Compiler* compiler, bool _){
|
|||
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;
|
||||
|
|
@ -52,12 +50,10 @@ private void number(Compiler* compiler, bool _){
|
|||
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.from(value));
|
||||
}
|
||||
private void literal(Compiler* compiler, bool _){
|
||||
Token token = compiler.parser.previous;
|
||||
compiler.emitter.setLine(token.line);
|
||||
switch(token.type){
|
||||
case Token.Type.True: compiler.emitter.emit(OpCode.True); break;
|
||||
case Token.Type.False: compiler.emitter.emit(OpCode.False); break;
|
||||
|
|
@ -69,12 +65,29 @@ private void strlit(Compiler* compiler, bool _){
|
|||
Token token = compiler.parser.previous;
|
||||
const char[] str = token.lexeme[1 .. $-1];
|
||||
Obj.String* strObj = Obj.String.copy(str);
|
||||
compiler.emitter.setLine(token.line);
|
||||
compiler.emitter.emitConstant(Value.from(strObj));
|
||||
}
|
||||
private void variable(Compiler* compiler, bool canAssign){
|
||||
compiler.parser.namedVariable(compiler.parser.previous, canAssign);
|
||||
}
|
||||
private void and(Compiler* compiler, bool _){
|
||||
int endJump = compiler.parser.emitJump(OpCode.JumpIfFalse);
|
||||
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
compiler.parser.parsePrecedence(Precedence.And);
|
||||
|
||||
compiler.parser.patchJump(endJump);
|
||||
}
|
||||
private void or(Compiler* compiler, bool _){
|
||||
int elseJump = compiler.parser.emitJump(OpCode.JumpIfFalse);
|
||||
int endJump = compiler.parser.emitJump(OpCode.Jump);
|
||||
|
||||
compiler.parser.patchJump(elseJump);
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
|
||||
compiler.parser.parsePrecedence(Precedence.Or);
|
||||
compiler.parser.patchJump(endJump);
|
||||
}
|
||||
|
||||
|
||||
struct ParseRule{
|
||||
|
|
@ -123,7 +136,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
|||
Token.Type.Identifier : ParseRule(&variable, null, Precedence.None),
|
||||
Token.Type.String : ParseRule(&strlit, null, Precedence.None),
|
||||
Token.Type.Number : ParseRule(&number, null, Precedence.None),
|
||||
Token.Type.And : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.And : ParseRule(null, &and, Precedence.And),
|
||||
Token.Type.Class : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Else : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.False : ParseRule(&literal, null, Precedence.None),
|
||||
|
|
@ -131,7 +144,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
|||
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.Or : ParseRule(null, &or, Precedence.Or),
|
||||
Token.Type.Print : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Return : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Super : ParseRule(null, null, Precedence.None),
|
||||
|
|
|
|||
|
|
@ -53,21 +53,38 @@ struct Value{
|
|||
}
|
||||
int opCmp(Value rhs) const pure{
|
||||
final switch(type){
|
||||
case Type.Number: return asNumber > rhs.asNumber;
|
||||
case Type.Bool: return asBoolean > rhs.asBoolean;
|
||||
case Type.Number:
|
||||
double l = asNumber;
|
||||
double r = rhs.asNumber;
|
||||
if(l == r)
|
||||
return 0;
|
||||
return l > r ? 1 : -1;
|
||||
case Type.Bool: return asBoolean - rhs.asBoolean;
|
||||
case Type.Nil: return 0;
|
||||
case Type.Obj: return asObj > rhs.asObj;
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
int print() const{
|
||||
int print(const char* strFmt = `"%s"`) const{
|
||||
final switch(type){
|
||||
case Type.Number: return printf("%g", asNumber);
|
||||
case Type.Bool: return printf(asBoolean ? "true" : "false");
|
||||
case Type.Nil: return printf("nil");
|
||||
case Type.Obj: return asObj.print();
|
||||
case Type.Obj: return asObj.print(strFmt);
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
version(D_BetterC) {} else {
|
||||
import std.conv : to;
|
||||
string toString(){
|
||||
final switch(type){
|
||||
case Type.Number: return asNumber.to!string;
|
||||
case Type.Bool: return asBoolean.to!string;
|
||||
case Type.Nil: return "nil";
|
||||
case Type.Obj: return Obj.toString(asObj);
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,12 +52,19 @@ struct VM{
|
|||
fprintf(stderr, format, args);
|
||||
fputs("\n", stderr);
|
||||
|
||||
size_t instruction = vm.ip - vm.chunk.code.ptr - 1;
|
||||
uint line = vm.chunk.lines[instruction];
|
||||
size_t instruction = ip - chunk.code.ptr - 1;
|
||||
uint line = chunk.lines[instruction];
|
||||
fprintf(stderr, "[line %d] in script\n", line);
|
||||
}
|
||||
InterpretResult run(){
|
||||
debug(traceExec){
|
||||
printHeader(chunk);
|
||||
}
|
||||
ubyte readByte() => *ip++;
|
||||
ushort readShort(){
|
||||
ip += 2;
|
||||
return (ip[-2] << 8) | ip[-1];
|
||||
}
|
||||
long readVarUint(){
|
||||
VarUint vi = VarUint.read(ip);
|
||||
ip += vi.len;
|
||||
|
|
@ -80,7 +87,7 @@ struct VM{
|
|||
}
|
||||
while(true){
|
||||
debug(traceExec){
|
||||
disassembleInstruction(vm.chunk, vm.ip - vm.chunk.code.ptr);
|
||||
disassembleInstruction(chunk, ip - chunk.code.ptr);
|
||||
printf(" ");
|
||||
foreach(slot; stack.live){
|
||||
printf("");
|
||||
|
|
@ -100,6 +107,20 @@ struct VM{
|
|||
case OpCode.False: stack ~= Value.from(false); break;
|
||||
case OpCode.Pop: stack.pop(); break;
|
||||
|
||||
case OpCode.Jump:
|
||||
ushort offset = readShort();
|
||||
ip += offset;
|
||||
break;
|
||||
case OpCode.JumpIfFalse:
|
||||
ushort offset = readShort();
|
||||
if(peek(0).isFalsey)
|
||||
ip += offset;
|
||||
break;
|
||||
case OpCode.Loop:
|
||||
ushort offset = readShort();
|
||||
ip -= offset;
|
||||
break;
|
||||
|
||||
case OpCode.GetLocal:
|
||||
long slot = readVarUint();
|
||||
stack ~= stack[slot];
|
||||
|
|
@ -170,7 +191,7 @@ struct VM{
|
|||
stack ~= Value.from(-stack.pop().asNumber);
|
||||
break;
|
||||
case OpCode.Print:
|
||||
stack.pop().print();
|
||||
stack.pop().print("%s");
|
||||
printf("\n");
|
||||
break;
|
||||
case OpCode.Return:
|
||||
|
|
|
|||
9
test/for.lox
Normal file
9
test/for.lox
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
for(var x = 1; x < 3; x = x + 1){
|
||||
print x;
|
||||
}
|
||||
|
||||
for(var x = 1; x <= 3; x = x + 1){
|
||||
print x;
|
||||
}
|
||||
|
||||
23
test/ifelse.lox
Normal file
23
test/ifelse.lox
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
if(true){
|
||||
print "a1";
|
||||
} else {
|
||||
print "b1";
|
||||
}
|
||||
|
||||
if(false){
|
||||
print "a2";
|
||||
} else {
|
||||
print "b2";
|
||||
}
|
||||
|
||||
if(false){
|
||||
print "a3";
|
||||
} else if(false){
|
||||
print "b3";
|
||||
} else if(true){
|
||||
print "c3";
|
||||
} else {
|
||||
print "d3";
|
||||
}
|
||||
|
||||
42
test/while.lox
Normal file
42
test/while.lox
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
var x = 1;
|
||||
|
||||
while(x <= 2){
|
||||
print x;
|
||||
x = x + 1;
|
||||
}
|
||||
print "";
|
||||
|
||||
while(x > 0){
|
||||
print x;
|
||||
x = x - 1;
|
||||
}
|
||||
print "";
|
||||
|
||||
while(x < 2){
|
||||
print x;
|
||||
x = x + 1;
|
||||
}
|
||||
print "";
|
||||
|
||||
{
|
||||
var x = 1;
|
||||
|
||||
while(x <= 2){
|
||||
print x;
|
||||
x = x + 1;
|
||||
}
|
||||
print "";
|
||||
|
||||
while(x > 0){
|
||||
print x;
|
||||
x = x - 1;
|
||||
}
|
||||
print "";
|
||||
|
||||
while(x < 2){
|
||||
print x;
|
||||
x = x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue