Types of Values 18
This commit is contained in:
parent
41404633da
commit
10c44eab2e
7 changed files with 230 additions and 28 deletions
|
|
@ -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;
|
||||
|
|
|
|||
59
src/clox/container/vartype.d
Normal file
59
src/clox/container/vartype.d
Normal 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);
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue