219 lines
5.9 KiB
D
219 lines
5.9 KiB
D
module jlox.interpreter;
|
|
|
|
import std.conv;
|
|
import std.stdio;
|
|
import std.algorithm;
|
|
import std.array;
|
|
import std.format : format;
|
|
import std.functional : ctEval;
|
|
|
|
import jlox.expr;
|
|
import jlox.stmt;
|
|
import jlox.token;
|
|
import jlox.tokentype;
|
|
import jlox.token : TValue;
|
|
import jlox.main;
|
|
import jlox.environment;
|
|
import jlox.loxfunction;
|
|
|
|
class RuntimeError : Exception{
|
|
const Token token;
|
|
this(Token token, string message){
|
|
super(message);
|
|
this.token = token;
|
|
}
|
|
}
|
|
class Return : Exception{
|
|
const TValue value;
|
|
this(TValue value){
|
|
super(null);
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
|
Environment globals = new Environment();
|
|
private Environment environment;
|
|
this(){
|
|
environment = globals;
|
|
|
|
import std.datetime.stopwatch;
|
|
auto sw = StopWatch(AutoStart.yes);
|
|
globals.define("clock", TValue.cal(new class LoxCallable{
|
|
int arity() => 0;
|
|
TValue call(Interpreter interpreter, TValue[] arguments) => TValue.dbl(sw.peek.total!"usecs" / (1000.0 * 1000.0));
|
|
}));
|
|
|
|
version(LoxExtraNativeFuncs){
|
|
globals.define("sleep", TValue.cal(new class LoxCallable{
|
|
int arity() => 1;
|
|
import core.thread.osthread;
|
|
TValue call(Interpreter interpreter, TValue[] arguments){
|
|
Thread.sleep(dur!"usecs"(cast(long)(arguments[0].dblValue * 1000 * 1000)));
|
|
return TValue.nil(tvalueNil);
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
void interpret(Stmt[] statements){
|
|
try {
|
|
foreach(statement; statements)
|
|
execute(statement);
|
|
} catch(RuntimeError error){
|
|
Lox.runtimeError(error);
|
|
}
|
|
}
|
|
package void execute(Stmt stmt){
|
|
stmt.accept(this);
|
|
}
|
|
package void executeBlock(Stmt[] statements, Environment environment){
|
|
Environment previous = this.environment;
|
|
try {
|
|
this.environment = environment;
|
|
foreach(Stmt statement; statements)
|
|
execute(statement);
|
|
} finally {
|
|
this.environment = previous;
|
|
}
|
|
}
|
|
private TValue evaluate(Expr expr){
|
|
return expr.accept(this);
|
|
}
|
|
private bool isTruthy(TValue val){
|
|
switch(val.kind){
|
|
case TValue.Kind.nil:
|
|
return false;
|
|
case TValue.Kind.bln:
|
|
return val.blnValue;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
private bool isEqual(TValue a, TValue b){
|
|
return a == b;
|
|
}
|
|
private void checkNumberOperand(Token operator, TValue operand){
|
|
if(operand.kind == TValue.Kind.dbl)
|
|
return;
|
|
throw new RuntimeError(operator, "Operand must be a number.");
|
|
}
|
|
|
|
|
|
void visit(Stmt.Block stmt){
|
|
executeBlock(stmt.statements, new Environment(environment));
|
|
}
|
|
void visit(Stmt.Expression stmt){
|
|
evaluate(stmt.expression);
|
|
}
|
|
void visit(Stmt.Function stmt){
|
|
LoxFunction func = new LoxFunction(stmt);
|
|
environment.define(stmt.name.lexeme, TValue.cal(func));
|
|
}
|
|
void visit(Stmt.If stmt){
|
|
if(isTruthy(evaluate(stmt.condition)))
|
|
execute(stmt.thenBranch);
|
|
else if(stmt.elseBranch)
|
|
execute(stmt.elseBranch);
|
|
}
|
|
void visit(Stmt.Print stmt){
|
|
TValue value = evaluate(stmt.expression);
|
|
writeln(tvalueToString(value));
|
|
}
|
|
void visit(Stmt.Return stmt){
|
|
TValue value = stmt.value !is null ? evaluate(stmt.value) : TValue.nil(tvalueNil);
|
|
throw new Return(value);
|
|
}
|
|
void visit(Stmt.Var stmt){
|
|
environment.define(stmt.name.lexeme, stmt.initialiser is null ? TValue.nil(tvalueNil) : evaluate(stmt.initialiser));
|
|
}
|
|
void visit(Stmt.While stmt){
|
|
while(isTruthy(evaluate(stmt.condition)))
|
|
execute(stmt.body);
|
|
}
|
|
|
|
TValue visit(Expr.Literal expr){
|
|
return expr.value;
|
|
}
|
|
TValue visit(Expr.Grouping expr){
|
|
return evaluate(expr.expression);
|
|
}
|
|
TValue visit(Expr.Unary expr){
|
|
TValue right = evaluate(expr.right);
|
|
switch(expr.operator.type){
|
|
case TokenType.MINUS:
|
|
checkNumberOperand(expr.operator, right);
|
|
return TValue.dbl(-right.dblValue);
|
|
case TokenType.BANG:
|
|
return TValue.bln(!isTruthy(right));
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
TValue visit(Expr.Logical expr){
|
|
TValue left = evaluate(expr.left);
|
|
if(expr.operator.type == TokenType.OR){
|
|
if(isTruthy(left))
|
|
return left;
|
|
} else {
|
|
if(!isTruthy(left))
|
|
return left;
|
|
}
|
|
return evaluate(expr.right);
|
|
}
|
|
TValue visit(Expr.Binary expr){
|
|
TValue left = evaluate(expr.left);
|
|
TValue right = evaluate(expr.right);
|
|
static string m(TokenType t, string op, string v, string vv){
|
|
return q{case %s:
|
|
checkNumberOperand(expr.operator, left);
|
|
checkNumberOperand(expr.operator, right);
|
|
return TValue.%s( left.%s %s right.%s );
|
|
}.format(t, v, vv, op, vv);
|
|
}
|
|
with(TokenType) switch(expr.operator.type){
|
|
static foreach(t, op; [ MINUS: "-", SLASH: "/", STAR: "*" ])
|
|
mixin(ctEval!(m(t, op, "dbl", "dblValue")));
|
|
|
|
case PLUS:
|
|
if(left.isDbl && right.isDbl)
|
|
return TValue.dbl(left.dblValue + right.dblValue);
|
|
else if(left.isStr && right.isStr)
|
|
return TValue.str(left.strValue ~ right.strValue);
|
|
version(LoxConcatNonStrings){
|
|
if(left.isStr || right.isStr)
|
|
return TValue.str(tvalueToString(left) ~ tvalueToString(right));
|
|
}
|
|
checkNumberOperand(expr.operator, left);
|
|
checkNumberOperand(expr.operator, right);
|
|
|
|
static foreach(t, op; [ GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ])
|
|
mixin(ctEval!(m(t, op, "bln", "dblValue")));
|
|
|
|
case BANG_EQUAL:
|
|
return TValue.bln(!isEqual(left, right));
|
|
case EQUAL_EQUAL:
|
|
return TValue.bln(isEqual(left, right));
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
TValue visit(Expr.Call expr){
|
|
TValue callee = evaluate(expr.callee);
|
|
if(!callee.isCal)
|
|
throw new RuntimeError(expr.paren, "Can only call functions and classes.");
|
|
auto arguments = expr.arguments.map!(a => evaluate(a));
|
|
LoxCallable func = callee.calValue;
|
|
if(arguments.length != func.arity())
|
|
throw new RuntimeError(expr.paren, "Expected " ~ func.arity().to!string ~ " arguments but got " ~ arguments.length.to!string ~ ".");
|
|
return func.call(this, arguments.array);
|
|
}
|
|
TValue visit(Expr.Variable expr){
|
|
return environment.get(expr.name);
|
|
}
|
|
TValue visit(Expr.Assign expr){
|
|
TValue value = evaluate(expr.value);
|
|
environment.assign(expr.name, value);
|
|
return value;
|
|
}
|
|
}
|
|
|