From 10c44eab2ecc3a492fe0de24d47eb584295cd99c Mon Sep 17 00:00:00 2001 From: nazrin Date: Thu, 5 Jun 2025 00:55:26 +0000 Subject: [PATCH] Types of Values 18 --- src/clox/chunk.d | 36 ++++++++++++++++++++-- src/clox/container/vartype.d | 59 ++++++++++++++++++++++++++++++++++++ src/clox/dbg.d | 18 ++++++++--- src/clox/emitter.d | 2 +- src/clox/parserules.d | 50 +++++++++++++++++++++--------- src/clox/value.d | 47 ++++++++++++++++++++++++++-- src/clox/vm.d | 46 ++++++++++++++++++++++++++-- 7 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 src/clox/container/vartype.d diff --git a/src/clox/chunk.d b/src/clox/chunk.d index 833e8b1..e41bcaf 100644 --- a/src/clox/chunk.d +++ b/src/clox/chunk.d @@ -8,12 +8,42 @@ 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, - Add, Subtract, Multiply, Divide, - Negate, - Return, + @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; diff --git a/src/clox/container/vartype.d b/src/clox/container/vartype.d new file mode 100644 index 0000000..1a27cc9 --- /dev/null +++ b/src/clox/container/vartype.d @@ -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); +} + diff --git a/src/clox/dbg.d b/src/clox/dbg.d index 9d88850..570efc5 100644 --- a/src/clox/dbg.d +++ b/src/clox/dbg.d @@ -13,8 +13,17 @@ import clox.util; import clox.container.varint; import clox.container.int24; -private ulong simpleInstruction(string name, ulong offset){ - writeln(name.lightCyan); +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){ @@ -41,12 +50,13 @@ ulong disassembleInstruction(Chunk* chunk, const ulong offset){ } 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; [ Return, Negate, Add, Subtract, Multiply, Divide ]){ + static foreach(k; Filter!(isSize1Op, EnumMembers!OpCode)){ case k: static name = "OP_" ~ (k.to!string).toUpper; - return simpleInstruction(name, offset); + return simpleInstruction!k(name, offset); } default: writefln("Unknown opcode %d", instruction); diff --git a/src/clox/emitter.d b/src/clox/emitter.d index 480f3c6..116f504 100644 --- a/src/clox/emitter.d +++ b/src/clox/emitter.d @@ -11,7 +11,7 @@ import clox.container.varint; struct Emitter{ Compiler* compiler; Chunk* chunk; - private uint line; + private uint line = 1; Chunk* currentChunk(){ return chunk; } diff --git a/src/clox/parserules.d b/src/clox/parserules.d index 3f8e315..a8bff9e 100644 --- a/src/clox/parserules.d +++ b/src/clox/parserules.d @@ -3,30 +3,32 @@ module clox.parserules; import clox.compiler; import clox.chunk; import clox.scanner; +import clox.value; alias ParseFn = void function(Compiler* compiler); -void number(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); + compiler.emitter.emitConstant(Value.num(value)); } -void grouping(Compiler* compiler){ +private void grouping(Compiler* compiler){ compiler.parser.expression(); compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression."); } -void unary(Compiler* compiler){ +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); } } -void binary(Compiler* compiler){ +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)); @@ -36,6 +38,24 @@ void binary(Compiler* compiler){ 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); } } @@ -75,31 +95,31 @@ immutable ParseRule[Token.Type.max+1] rules = [ 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(null, null, Precedence.None), - Token.Type.BangEqual : ParseRule(null, null, Precedence.None), + 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, null, Precedence.None), - Token.Type.Greater : ParseRule(null, null, Precedence.None), - Token.Type.GreaterEqual : ParseRule(null, null, Precedence.None), - Token.Type.Less : ParseRule(null, null, Precedence.None), - Token.Type.LessEqual : 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(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(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(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), diff --git a/src/clox/value.d b/src/clox/value.d index c9b298c..1914616 100644 --- a/src/clox/value.d +++ b/src/clox/value.d @@ -2,9 +2,52 @@ module clox.value; import std.stdio; -alias Value = double; +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){ - writef("%g", 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); + } } diff --git a/src/clox/vm.d b/src/clox/vm.d index 67a5090..485a626 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -9,6 +9,7 @@ import clox.util; import clox.compiler; import clox.container.stack; import clox.container.varint; +import clox.container.int24; enum stackMax = 256; @@ -33,7 +34,16 @@ struct VM{ ip = &chunk.code[0]; return run(); } - private InterpretResult run() @nogc nothrow { + 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(){ @@ -41,6 +51,9 @@ struct VM{ 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); @@ -52,15 +65,42 @@ struct VM{ 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(mixin("a", op, "b")); + stack.push(Value.bln(compare!op(a, b))); break opSwitch; } + case Not: + stack.push(Value.bln(stack.pop().isFalsey)); + break; case Negate: - stack.push(-stack.pop()); + 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());