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