Types of Values 18

This commit is contained in:
nazrin 2025-06-05 00:55:26 +00:00
parent 41404633da
commit 10c44eab2e
7 changed files with 230 additions and 28 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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