Jumping Back and Forth 23

This commit is contained in:
nazrin 2025-06-08 05:32:04 +00:00
parent 98e7f950cf
commit 8717d37445
13 changed files with 299 additions and 30 deletions

View file

@ -9,10 +9,10 @@ targetType "executable"
sourcePaths sourcePaths
configuration "clox" { configuration "clox" {
/* buildOptions "betterC" */ buildOptions "betterC"
debugVersions "memTrace" /* debugVersions "memTrace" */
/* debugVersions "traceExec" */ /* debugVersions "traceExec" */
/* debugVersions "printCode" */ debugVersions "printCode"
dflags "-checkaction=C" dflags "-checkaction=C"
libs "libbacktrace" libs "libbacktrace"
targetType "executable" targetType "executable"

View file

@ -23,6 +23,10 @@ enum OpCode : ubyte{
@(OpColour("060", "200", "150")) SetLocal, @(OpColour("060", "200", "150")) SetLocal,
@(OpColour("000", "200", "150")) SetGlobal, @(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")) Equal,
@(OpColour("255", "100", "100")) Greater, @(OpColour("255", "100", "100")) Greater,
@(OpColour("255", "100", "100")) Less, @(OpColour("255", "100", "100")) Less,

View file

@ -24,6 +24,10 @@ struct DynArray(T){
assert(ptr || !count); assert(ptr || !count);
return ptr[0 .. 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){ ref auto opIndex(size_t i){
assert(ptr && count); assert(ptr && count);
return ptr[i]; return ptr[i];

View file

@ -25,9 +25,32 @@ private long constantInstruction(alias OpCode op)(const char* name, Chunk* chunk
printf(" "); printf(" ");
return offset + 1 + constant.len; 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"){ void printHeader(Chunk* chunk, const char* name = "execution"){
printf(" == %s ==\n", name); 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;){ for(long offset = 0; offset < chunk.code.count;){
offset = disassembleInstruction(chunk, offset); offset = disassembleInstruction(chunk, offset);
printf("\n"); printf("\n");
@ -41,14 +64,18 @@ long disassembleInstruction(Chunk* chunk, long offset){
} else { } else {
printf(colour!("%4d ", Colour.Black).ptr, chunk.lines[offset]); 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){ switch(instruction){
static foreach(e; EnumMembers!OpCode){ 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); 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 { } else {
case e: case e: return simpleInstruction!e(e.stringof, offset);
return simpleInstruction!e(e.stringof, offset);
} }
} }
default: printf("(Unknown opcode %d)", instruction); default: printf("(Unknown opcode %d)", instruction);

View file

@ -9,8 +9,6 @@ import clox.container.varint;
struct Emitter{ struct Emitter{
Compiler* compiler; Compiler* compiler;
private uint line = 1;
uint setLine(uint line) => this.line = line;
void initialise(Compiler* compiler){ void initialise(Compiler* compiler){
this.compiler = compiler; this.compiler = compiler;
} }

View file

@ -22,9 +22,9 @@ struct Obj{
static if(type == Type.None) return &this; static if(type == Type.None) return &this;
} }
int print() const{ int print(const char* strFmt = `"%s"`) const{
final switch(type){ 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); case Type.None: assert(0);
} }
} }
@ -95,5 +95,14 @@ struct Obj{
case Type.None: assert(0); 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);
}
}
}
} }

View file

@ -60,10 +60,11 @@ struct Parser{
} }
void varDeclaration(){ void varDeclaration(){
long global = parseVariable("Expect variable name."); long global = parseVariable("Expect variable name.");
if(match(Token.Type.Equal)) if(match(Token.Type.Equal)){
expression(); expression();
else } else {
compiler.emitter.emit(OpCode.Nil); compiler.emitter.emit(OpCode.Nil);
}
consume(Token.Type.Semicolon, "Expect ';' after variable declaration."); consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
defineVariable(global); defineVariable(global);
} }
@ -78,6 +79,12 @@ struct Parser{
void statement(){ void statement(){
if(match(Token.Type.Print)){ if(match(Token.Type.Print)){
printStatement(); 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)){ } else if(match(Token.Type.LeftBrace)){
beginScope(); beginScope();
block(); block();
@ -90,12 +97,107 @@ struct Parser{
consume(Token.Type.Semicolon, "Expect ';' after value."); consume(Token.Type.Semicolon, "Expect ';' after value.");
compiler.emitter.emit(OpCode.Print); 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(){ void block(){
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF)) while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF))
declaration(); declaration();
consume(Token.Type.RightBrace, "Expect '}' after block."); 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){ void defineVariable(long global){
if(compiler.scopeDepth > 0){ if(compiler.scopeDepth > 0){
markInitialised(); markInitialised();

View file

@ -17,7 +17,6 @@ private void grouping(Compiler* compiler, bool _){
private void unary(Compiler* compiler, bool _){ private void unary(Compiler* compiler, bool _){
Token operator = compiler.parser.previous; Token operator = compiler.parser.previous;
compiler.parser.parsePrecedence(Precedence.Unary); compiler.parser.parsePrecedence(Precedence.Unary);
compiler.emitter.setLine(operator.line);
switch(operator.type){ switch(operator.type){
case Token.Type.Minus: compiler.emitter.emit(OpCode.Negate); break; case Token.Type.Minus: compiler.emitter.emit(OpCode.Negate); break;
case Token.Type.Bang: compiler.emitter.emit(OpCode.Not); 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; Token operator = compiler.parser.previous;
immutable(ParseRule)* rule = ParseRule.get(operator.type); immutable(ParseRule)* rule = ParseRule.get(operator.type);
compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1)); compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1));
compiler.emitter.setLine(operator.line);
switch(operator.type){ switch(operator.type){
case Token.Type.Plus: compiler.emitter.emit(OpCode.Add); break; case Token.Type.Plus: compiler.emitter.emit(OpCode.Add); break;
case Token.Type.Minus: compiler.emitter.emit(OpCode.Subtract); 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; import core.stdc.stdlib : strtod;
Token token = compiler.parser.previous; Token token = compiler.parser.previous;
double value = strtod(token.lexeme.ptr, null); double value = strtod(token.lexeme.ptr, null);
compiler.emitter.setLine(token.line);
compiler.emitter.emitConstant(Value.from(value)); compiler.emitter.emitConstant(Value.from(value));
} }
private void literal(Compiler* compiler, bool _){ private void literal(Compiler* compiler, bool _){
Token token = compiler.parser.previous; Token token = compiler.parser.previous;
compiler.emitter.setLine(token.line);
switch(token.type){ switch(token.type){
case Token.Type.True: compiler.emitter.emit(OpCode.True); break; case Token.Type.True: compiler.emitter.emit(OpCode.True); break;
case Token.Type.False: compiler.emitter.emit(OpCode.False); 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; Token token = compiler.parser.previous;
const char[] str = token.lexeme[1 .. $-1]; const char[] str = token.lexeme[1 .. $-1];
Obj.String* strObj = Obj.String.copy(str); Obj.String* strObj = Obj.String.copy(str);
compiler.emitter.setLine(token.line);
compiler.emitter.emitConstant(Value.from(strObj)); compiler.emitter.emitConstant(Value.from(strObj));
} }
private void variable(Compiler* compiler, bool canAssign){ private void variable(Compiler* compiler, bool canAssign){
compiler.parser.namedVariable(compiler.parser.previous, 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{ struct ParseRule{
@ -123,7 +136,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
Token.Type.Identifier : ParseRule(&variable, null, Precedence.None), Token.Type.Identifier : ParseRule(&variable, null, Precedence.None),
Token.Type.String : ParseRule(&strlit, null, Precedence.None), Token.Type.String : ParseRule(&strlit, null, Precedence.None),
Token.Type.Number : ParseRule(&number, 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.Class : ParseRule(null, null, Precedence.None),
Token.Type.Else : ParseRule(null, null, Precedence.None), Token.Type.Else : ParseRule(null, null, Precedence.None),
Token.Type.False : ParseRule(&literal, 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.Fun : ParseRule(null, null, Precedence.None),
Token.Type.If : ParseRule(null, null, Precedence.None), Token.Type.If : ParseRule(null, null, Precedence.None),
Token.Type.Nil : ParseRule(&literal, 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.Print : ParseRule(null, null, Precedence.None),
Token.Type.Return : ParseRule(null, null, Precedence.None), Token.Type.Return : ParseRule(null, null, Precedence.None),
Token.Type.Super : ParseRule(null, null, Precedence.None), Token.Type.Super : ParseRule(null, null, Precedence.None),

View file

@ -53,21 +53,38 @@ struct Value{
} }
int opCmp(Value rhs) const pure{ int opCmp(Value rhs) const pure{
final switch(type){ final switch(type){
case Type.Number: return asNumber > rhs.asNumber; case Type.Number:
case Type.Bool: return asBoolean > rhs.asBoolean; 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.Nil: return 0;
case Type.Obj: return asObj > rhs.asObj; case Type.Obj: return asObj > rhs.asObj;
case Type.None: assert(0); case Type.None: assert(0);
} }
} }
int print() const{ int print(const char* strFmt = `"%s"`) const{
final switch(type){ final switch(type){
case Type.Number: return printf("%g", asNumber); case Type.Number: return printf("%g", asNumber);
case Type.Bool: return printf(asBoolean ? "true" : "false"); case Type.Bool: return printf(asBoolean ? "true" : "false");
case Type.Nil: return printf("nil"); 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); case Type.None: assert(0);
} }
} }
} }
}

View file

@ -52,12 +52,19 @@ struct VM{
fprintf(stderr, format, args); fprintf(stderr, format, args);
fputs("\n", stderr); fputs("\n", stderr);
size_t instruction = vm.ip - vm.chunk.code.ptr - 1; size_t instruction = ip - chunk.code.ptr - 1;
uint line = vm.chunk.lines[instruction]; uint line = chunk.lines[instruction];
fprintf(stderr, "[line %d] in script\n", line); fprintf(stderr, "[line %d] in script\n", line);
} }
InterpretResult run(){ InterpretResult run(){
debug(traceExec){
printHeader(chunk);
}
ubyte readByte() => *ip++; ubyte readByte() => *ip++;
ushort readShort(){
ip += 2;
return (ip[-2] << 8) | ip[-1];
}
long readVarUint(){ long readVarUint(){
VarUint vi = VarUint.read(ip); VarUint vi = VarUint.read(ip);
ip += vi.len; ip += vi.len;
@ -80,7 +87,7 @@ struct VM{
} }
while(true){ while(true){
debug(traceExec){ debug(traceExec){
disassembleInstruction(vm.chunk, vm.ip - vm.chunk.code.ptr); disassembleInstruction(chunk, ip - chunk.code.ptr);
printf(" "); printf(" ");
foreach(slot; stack.live){ foreach(slot; stack.live){
printf(""); printf("");
@ -100,6 +107,20 @@ struct VM{
case OpCode.False: stack ~= Value.from(false); break; case OpCode.False: stack ~= Value.from(false); break;
case OpCode.Pop: stack.pop(); 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: case OpCode.GetLocal:
long slot = readVarUint(); long slot = readVarUint();
stack ~= stack[slot]; stack ~= stack[slot];
@ -170,7 +191,7 @@ struct VM{
stack ~= Value.from(-stack.pop().asNumber); stack ~= Value.from(-stack.pop().asNumber);
break; break;
case OpCode.Print: case OpCode.Print:
stack.pop().print(); stack.pop().print("%s");
printf("\n"); printf("\n");
break; break;
case OpCode.Return: case OpCode.Return:

9
test/for.lox Normal file
View 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
View 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
View 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;
}
}