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(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
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,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;
}

View file

@ -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(){

View file

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

View file

@ -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;