Evaluating Expressions 7

This commit is contained in:
nazrin 2025-05-31 17:44:11 +00:00
parent d937226553
commit f4338ba51f
6 changed files with 182 additions and 63 deletions

View file

@ -17,7 +17,7 @@ abstract class Expr{
R visit(Literal expr); R visit(Literal expr);
R visit(Unary expr); R visit(Unary expr);
} }
private alias rTypes = AliasSeq!(string); private alias rTypes = AliasSeq!(string, TValue);
static foreach(T; rTypes) static foreach(T; rTypes)
abstract T accept(Visitor!T visitor); abstract T accept(Visitor!T visitor);
private template defCtorAndAccept(){ private template defCtorAndAccept(){
@ -37,7 +37,7 @@ abstract class Expr{
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Literal : Expr{ static class Literal : Expr{
TTokenValue value; TValue value;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Unary : Expr{ static class Unary : Expr{
@ -78,9 +78,9 @@ class AstPrinter : Expr.Visitor!string{
unittest{ unittest{
auto expression = new Expr.Binary( auto expression = new Expr.Binary(
new Expr.Unary( new Expr.Unary(
new Token(TokenType.MINUS, "-", TTokenValue.nil(0), 1), new Token(TokenType.MINUS, "-", TValue.nil(0), 1),
new Expr.Literal(123)), 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.Grouping(
new Expr.Literal(45.67))); new Expr.Literal(45.67)));
assert(new AstPrinter().print(expression)); assert(new AstPrinter().print(expression));

108
src/jlox/interpreter.d Normal file
View 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);
}
}
}

View file

@ -12,25 +12,35 @@ import jlox.tokentype;
import jlox.scanner; import jlox.scanner;
import jlox.parser; import jlox.parser;
import jlox.expr; import jlox.expr;
import jlox.interpreter;
static bool hadError = false;
void error(int line, string 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); report(line, "", message);
} }
void error(Token token, string message){ static void error(Token token, string message){
if (token.type == TokenType.EOF) { if (token.type == TokenType.EOF) {
report(token.line, " at end", message); report(token.line, " at end", message);
} else { } else {
report(token.line, " at '" ~ token.lexeme ~ "'", message); report(token.line, " at '" ~ token.lexeme ~ "'", message);
} }
} }
void report(int line, string where, string message){ static void report(int line, string where, string message){
stderr.writeln("[line " ~ line.to!string ~ "] Error" ~ where ~ ": " ~ message); stderr.writeln("[line " ~ line.to!string ~ "] Error" ~ where ~ ": " ~ message);
hadError = true; hadError = true;
} }
static void runtimeError(RuntimeError error){
stderr.writeln(error.msg ~ "\n[line " ~ error.token.line.to!string ~ "]");
hadRuntimeError = true;
}
void run(string source){ static void run(string source){
Scanner scanner = new Scanner(source); Scanner scanner = new Scanner(source);
Token[] tokens = scanner.scanTokens(); Token[] tokens = scanner.scanTokens();
@ -40,16 +50,15 @@ void run(string source){
if(hadError) if(hadError)
return; return;
writeln((new AstPrinter).print(expression)); /* writeln((new AstPrinter).print(expression)); */
interpreter.interpret(expression);
} }
static void runFile(string path){
void runFile(string path){
string bytes = readText(path); string bytes = readText(path);
run(bytes); run(bytes);
enforce(!hadError); enforce(!hadError && !hadRuntimeError);
} }
static void runPrompt(){
void runPrompt(){
while(true){ while(true){
write("lox> "); write("lox> ");
string line = stdin.readln(); string line = stdin.readln();
@ -60,15 +69,16 @@ void runPrompt(){
} }
writeln(); writeln();
} }
}
version(unittest) {} else int main(string[] argv){ int main(string[] argv){
auto args = new Program("lox") auto args = new Program("lox")
.add(new Argument("path").optional.acceptsFiles) .add(new Argument("path").optional.acceptsFiles)
.parse(argv); .parse(argv);
if(args.arg("path")) if(args.arg("path"))
runFile(args.arg("path")); Lox.runFile(args.arg("path"));
else else
runPrompt(); Lox.runPrompt();
return 0; return 0;
} }

View file

@ -6,7 +6,7 @@ import jlox.token;
import jlox.tokentype; import jlox.tokentype;
import jlox.util; import jlox.util;
import jlox.expr; import jlox.expr;
import jlox.main : loxError = error; import jlox.main;
class Parser{ class Parser{
private Token[] tokens; private Token[] tokens;
@ -54,7 +54,7 @@ class Parser{
} }
} }
private ParseError error(Token token, string message){ private ParseError error(Token token, string message){
loxError(token, message); Lox.error(token, message);
return new ParseError("hello"); return new ParseError("hello");
} }
private void synchronize(){ private void synchronize(){

View file

@ -5,7 +5,7 @@ import std.conv;
import jlox.token; import jlox.token;
import jlox.tokentype; import jlox.tokentype;
import jlox.main : error, report; import jlox.main;
private bool isAlpha_(dchar c) => c.isAlpha || c == '_'; private bool isAlpha_(dchar c) => c.isAlpha || c == '_';
private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_'; private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_';
@ -30,7 +30,7 @@ class Scanner {
current++; current++;
return true; 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]; string text = source[start .. current];
tokens ~= new Token(type, text, literal, line); tokens ~= new Token(type, text, literal, line);
} }
@ -49,12 +49,12 @@ class Scanner {
advance(); advance();
} }
if(isAtEnd){ if(isAtEnd){
error(line, "Unterminated string."); Lox.error(line, "Unterminated string.");
return; return;
} }
advance(); advance();
string value = source[start + 1 .. current -1]; string value = source[start + 1 .. current -1];
addToken(TokenType.STRING, TTokenValue.str(value)); addToken(TokenType.STRING, TValue.str(value));
} }
private void number(){ private void number(){
while(peek().isDigit) while(peek().isDigit)
@ -64,7 +64,7 @@ class Scanner {
while(peek().isDigit) while(peek().isDigit)
advance(); 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){ private TokenType keywords(string word){
with(TokenType) switch(word){ with(TokenType) switch(word){
@ -142,7 +142,7 @@ class Scanner {
identifier(); identifier();
break; break;
default: default:
error(line, "Unexpected character"); Lox.error(line, "Unexpected character");
} }
} }
} }

View file

@ -6,20 +6,21 @@ import taggedalgebraic;
import jlox.tokentype; import jlox.tokentype;
struct TokenValue{ private struct Value{
string str; string str;
double dbl; double dbl;
bool bln;
bool nil = false; bool nil = false;
} }
alias TTokenValue = TaggedUnion!TokenValue; alias TValue = TaggedUnion!Value;
class Token { class Token {
TokenType type; TokenType type;
string lexeme; string lexeme;
TTokenValue literal; TValue literal;
int line; int line;
this(TokenType type, string lexeme, TTokenValue literal, int line) { this(TokenType type, string lexeme, TValue literal, int line) {
this.type = type; this.type = type;
this.lexeme = lexeme; this.lexeme = lexeme;
this.literal = literal; this.literal = literal;