Evaluating Expressions 7
This commit is contained in:
parent
d937226553
commit
f4338ba51f
6 changed files with 182 additions and 63 deletions
|
|
@ -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));
|
||||
|
|
|
|||
108
src/jlox/interpreter.d
Normal file
108
src/jlox/interpreter.d
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
src/jlox/main.d
104
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue