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
configuration "clox" {
/* buildOptions "betterC" */
debugVersions "memTrace"
buildOptions "betterC"
/* debugVersions "memTrace" */
/* debugVersions "traceExec" */
/* debugVersions "printCode" */
debugVersions "printCode"
dflags "-checkaction=C"
libs "libbacktrace"
targetType "executable"

View file

@ -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,

View file

@ -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];

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
}
}
}

View file

@ -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();

View file

@ -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),

View file

@ -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);
}
}
}
}

View file

@ -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
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;
}
}