diff --git a/src/jlox/expr.d b/src/jlox/expr.d index 674a826..a13ef2e 100644 --- a/src/jlox/expr.d +++ b/src/jlox/expr.d @@ -17,7 +17,7 @@ abstract class Expr{ R visit(Literal expr); R visit(Unary expr); } - private alias rTypes = AliasSeq!(string); + private alias rTypes = AliasSeq!(string, TValue); static foreach(T; rTypes) abstract T accept(Visitor!T visitor); private template defCtorAndAccept(){ @@ -37,7 +37,7 @@ abstract class Expr{ mixin defCtorAndAccept; } static class Literal : Expr{ - TTokenValue value; + TValue value; mixin defCtorAndAccept; } static class Unary : Expr{ @@ -78,9 +78,9 @@ class AstPrinter : Expr.Visitor!string{ unittest{ auto expression = new Expr.Binary( new Expr.Unary( - new Token(TokenType.MINUS, "-", TTokenValue.nil(0), 1), + new Token(TokenType.MINUS, "-", TValue.nil(0), 1), new Expr.Literal(123)), - new Token(TokenType.STAR, "*", TTokenValue.nil(0), 1), + new Token(TokenType.STAR, "*", TValue.nil(0), 1), new Expr.Grouping( new Expr.Literal(45.67))); assert(new AstPrinter().print(expression)); diff --git a/src/jlox/interpreter.d b/src/jlox/interpreter.d new file mode 100644 index 0000000..f957116 --- /dev/null +++ b/src/jlox/interpreter.d @@ -0,0 +1,108 @@ +module jlox.interpreter; + +import std.conv; +import std.stdio; +import std.format : format; +import std.functional : ctEval; + +import taggedalgebraic; + +import jlox.expr; +import jlox.token; +import jlox.tokentype; +import jlox.token : TValue; +import jlox.main; + +class RuntimeError : Exception{ + const Token token; + this(Token token, string message){ + super(message); + this.token = token; + } +} + +class Interpreter : Expr.Visitor!TValue{ + void interpret(Expr expression) { + try { + TValue value = evaluate(expression); + writeln(value); + } catch(RuntimeError error){ + Lox.runtimeError(error); + } + } + 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."); + } + 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.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: 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: "*" ]){ + checkNumberOperand(expr.operator, left); + checkNumberOperand(expr.operator, right); + 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); + checkNumberOperand(expr.operator, left); + checkNumberOperand(expr.operator, right); + assert(0); + + static foreach(t, op; [ GREATER: ">", GREATER_EQUAL: ">=", LESS: "<", LESS_EQUAL: "<=" ]){ + checkNumberOperand(expr.operator, left); + checkNumberOperand(expr.operator, right); + 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); + } + } +} + diff --git a/src/jlox/main.d b/src/jlox/main.d index 08d0b6a..c15fb8b 100644 --- a/src/jlox/main.d +++ b/src/jlox/main.d @@ -12,63 +12,73 @@ import jlox.tokentype; import jlox.scanner; import jlox.parser; import jlox.expr; +import jlox.interpreter; -static bool hadError = false; -void error(int line, string message){ - report(line, "", message); -} -void error(Token token, string message){ - if (token.type == TokenType.EOF) { - report(token.line, " at end", message); - } else { - report(token.line, " at '" ~ token.lexeme ~ "'", message); +static class Lox{ + private static Interpreter interpreter; + static this(){ + interpreter = new Interpreter(); + } + static bool hadError, hadRuntimeError; + static void error(int line, string message){ + report(line, "", message); + } + static void error(Token token, string message){ + if (token.type == TokenType.EOF) { + report(token.line, " at end", message); + } else { + report(token.line, " at '" ~ token.lexeme ~ "'", message); + } + } + static void report(int line, string where, string message){ + stderr.writeln("[line " ~ line.to!string ~ "] Error" ~ where ~ ": " ~ message); + hadError = true; + } + static void runtimeError(RuntimeError error){ + stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]"); + hadRuntimeError = true; + } + + static void run(string source){ + Scanner scanner = new Scanner(source); + Token[] tokens = scanner.scanTokens(); + + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); + + if(hadError) + return; + + /* writeln((new AstPrinter).print(expression)); */ + interpreter.interpret(expression); + } + static void runFile(string path){ + string bytes = readText(path); + run(bytes); + enforce(!hadError && !hadRuntimeError); + } + static void runPrompt(){ + while(true){ + write("lox> "); + string line = stdin.readln(); + if(!line) + break; + run(line); + hadError = false; + } + writeln(); } } -void report(int line, string where, string message){ - stderr.writeln("[line " ~ line.to!string ~ "] Error" ~ where ~ ": " ~ message); - hadError = true; -} -void run(string source){ - Scanner scanner = new Scanner(source); - Token[] tokens = scanner.scanTokens(); - - Parser parser = new Parser(tokens); - Expr expression = parser.parse(); - - if(hadError) - return; - - writeln((new AstPrinter).print(expression)); -} - -void runFile(string path){ - string bytes = readText(path); - run(bytes); - enforce(!hadError); -} - -void runPrompt(){ - while(true){ - write("lox> "); - string line = stdin.readln(); - if(!line) - break; - run(line); - hadError = false; - } - writeln(); -} - -version(unittest) {} else int main(string[] argv){ +int main(string[] argv){ auto args = new Program("lox") .add(new Argument("path").optional.acceptsFiles) .parse(argv); if(args.arg("path")) - runFile(args.arg("path")); + Lox.runFile(args.arg("path")); else - runPrompt(); + Lox.runPrompt(); return 0; } diff --git a/src/jlox/parser.d b/src/jlox/parser.d index 09487c4..ad1410a 100644 --- a/src/jlox/parser.d +++ b/src/jlox/parser.d @@ -6,7 +6,7 @@ import jlox.token; import jlox.tokentype; import jlox.util; import jlox.expr; -import jlox.main : loxError = error; +import jlox.main; class Parser{ private Token[] tokens; @@ -54,7 +54,7 @@ class Parser{ } } private ParseError error(Token token, string message){ - loxError(token, message); + Lox.error(token, message); return new ParseError("hello"); } private void synchronize(){ diff --git a/src/jlox/scanner.d b/src/jlox/scanner.d index 894849a..0cb45e7 100644 --- a/src/jlox/scanner.d +++ b/src/jlox/scanner.d @@ -5,7 +5,7 @@ import std.conv; import jlox.token; import jlox.tokentype; -import jlox.main : error, report; +import jlox.main; private bool isAlpha_(dchar c) => c.isAlpha || c == '_'; private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_'; @@ -30,7 +30,7 @@ class Scanner { current++; return true; } - private void addToken(TokenType type, TTokenValue literal = TTokenValue.nil(0)){ + private void addToken(TokenType type, TValue literal = TValue.nil(0)){ string text = source[start .. current]; tokens ~= new Token(type, text, literal, line); } @@ -49,12 +49,12 @@ class Scanner { advance(); } if(isAtEnd){ - error(line, "Unterminated string."); + Lox.error(line, "Unterminated string."); return; } advance(); string value = source[start + 1 .. current -1]; - addToken(TokenType.STRING, TTokenValue.str(value)); + addToken(TokenType.STRING, TValue.str(value)); } private void number(){ while(peek().isDigit) @@ -64,7 +64,7 @@ class Scanner { while(peek().isDigit) advance(); } - addToken(TokenType.NUMBER, TTokenValue.dbl(source[start .. current].to!double)); + addToken(TokenType.NUMBER, TValue.dbl(source[start .. current].to!double)); } private TokenType keywords(string word){ with(TokenType) switch(word){ @@ -142,7 +142,7 @@ class Scanner { identifier(); break; default: - error(line, "Unexpected character"); + Lox.error(line, "Unexpected character"); } } } diff --git a/src/jlox/token.d b/src/jlox/token.d index c79dc95..92eb377 100644 --- a/src/jlox/token.d +++ b/src/jlox/token.d @@ -6,20 +6,21 @@ import taggedalgebraic; import jlox.tokentype; -struct TokenValue{ +private struct Value{ string str; double dbl; + bool bln; bool nil = false; } -alias TTokenValue = TaggedUnion!TokenValue; +alias TValue = TaggedUnion!Value; class Token { TokenType type; string lexeme; - TTokenValue literal; + TValue literal; int line; - this(TokenType type, string lexeme, TTokenValue literal, int line) { + this(TokenType type, string lexeme, TValue literal, int line) { this.type = type; this.lexeme = lexeme; this.literal = literal;