From 8717d3744561ccdce17645b107f8232fe41787af Mon Sep 17 00:00:00 2001 From: nazrin Date: Sun, 8 Jun 2025 05:32:04 +0000 Subject: [PATCH] Jumping Back and Forth 23 --- dub.sdl | 6 +- src/clox/chunk.d | 4 ++ src/clox/container/dynarray.d | 4 ++ src/clox/dbg.d | 39 +++++++++++-- src/clox/emitter.d | 2 - src/clox/obj.d | 13 ++++- src/clox/parser.d | 106 +++++++++++++++++++++++++++++++++- src/clox/parserules.d | 27 ++++++--- src/clox/value.d | 25 ++++++-- src/clox/vm.d | 29 ++++++++-- test/for.lox | 9 +++ test/ifelse.lox | 23 ++++++++ test/while.lox | 42 ++++++++++++++ 13 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 test/for.lox create mode 100644 test/ifelse.lox create mode 100644 test/while.lox diff --git a/dub.sdl b/dub.sdl index 45dbbb5..1325a01 100644 --- a/dub.sdl +++ b/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" diff --git a/src/clox/chunk.d b/src/clox/chunk.d index a045758..701ba15 100644 --- a/src/clox/chunk.d +++ b/src/clox/chunk.d @@ -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, diff --git a/src/clox/container/dynarray.d b/src/clox/container/dynarray.d index c13793a..5c67421 100644 --- a/src/clox/container/dynarray.d +++ b/src/clox/container/dynarray.d @@ -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]; diff --git a/src/clox/dbg.d b/src/clox/dbg.d index e3c274b..d33501a 100644 --- a/src/clox/dbg.d +++ b/src/clox/dbg.d @@ -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); diff --git a/src/clox/emitter.d b/src/clox/emitter.d index 9163165..64cdb09 100644 --- a/src/clox/emitter.d +++ b/src/clox/emitter.d @@ -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; } diff --git a/src/clox/obj.d b/src/clox/obj.d index a6f7c09..1a4bada 100644 --- a/src/clox/obj.d +++ b/src/clox/obj.d @@ -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); + } + } + } } diff --git a/src/clox/parser.d b/src/clox/parser.d index f343c31..828831c 100644 --- a/src/clox/parser.d +++ b/src/clox/parser.d @@ -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(); diff --git a/src/clox/parserules.d b/src/clox/parserules.d index 55fcaa8..946d075 100644 --- a/src/clox/parserules.d +++ b/src/clox/parserules.d @@ -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), diff --git a/src/clox/value.d b/src/clox/value.d index 8f01fb0..688fa86 100644 --- a/src/clox/value.d +++ b/src/clox/value.d @@ -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); + } + } + } } diff --git a/src/clox/vm.d b/src/clox/vm.d index bb08094..f31d65b 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -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: diff --git a/test/for.lox b/test/for.lox new file mode 100644 index 0000000..7119993 --- /dev/null +++ b/test/for.lox @@ -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; +} + diff --git a/test/ifelse.lox b/test/ifelse.lox new file mode 100644 index 0000000..b50050a --- /dev/null +++ b/test/ifelse.lox @@ -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"; +} + diff --git a/test/while.lox b/test/while.lox new file mode 100644 index 0000000..79cb913 --- /dev/null +++ b/test/while.lox @@ -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; + } +} +