Add expression evaluation

This commit is contained in:
Abdelrahman Said 2025-06-29 02:15:10 +01:00
parent 7b8903c19c
commit e352df9d7d
9 changed files with 256 additions and 2 deletions

View File

@ -14,6 +14,11 @@ void ErrorHandler::error(Token token, const std::string &message) {
}
}
void ErrorHandler::runtime_error(const RuntimeException &exception) {
std::cout << "RuntimeException [" << exception.token.line << "]: " << exception.what() << '\n';
had_runtime_error = true;
}
void ErrorHandler::report(int line, const std::string &where, const std::string &message) {
std::cout << "[line " << line << "] Error" << where << ": " << message << '\n';
had_error = true;

View File

@ -2,15 +2,29 @@
#define ERROR_HANDLER_HH
#include "tokenizer.hh"
#include <exception>
#include <string>
struct RuntimeException : public std::exception {
RuntimeException(Token token, const std::string& message) : token{token}, message{message} {}
const char* what() const noexcept override {
return message.c_str();
}
Token token;
private:
std::string message;
};
struct ErrorHandler {
ErrorHandler() : had_error{false} {};
void error(int line, const std::string &message);
void error(Token token, const std::string &message);
void runtime_error(const RuntimeException &exception);
void report(int line, const std::string &where, const std::string &message);
bool had_error;
bool had_runtime_error;
};
#endif

View File

@ -35,6 +35,9 @@ void run_file(const char *path) {
if (error_handler.had_error) {
exit(EX_DATAERR);
}
if (error_handler.had_runtime_error) {
exit(EX_SOFTWARE);
}
}
}
@ -45,6 +48,7 @@ void run_prompt() {
run(line);
std::cout << PROMPT;
error_handler.had_error = false;
error_handler.had_runtime_error = false;
}
}
@ -55,8 +59,13 @@ void run(const std::string &code) {
Parser parser{tokens};
std::optional<Expr> expression = parser.parse();
static AstInterpreter interpreter{};
if (error_handler.had_error) {
return;
}
if (expression) {
AstPrinter printer{};
std::cout << printer.print(*expression) << '\n';
interpreter.interpret(*expression);
}
}

View File

@ -4,5 +4,6 @@
#include "parser/expr.cc"
#include "parser/printer.cc"
#include "parser/parser.cc"
#include "parser/interpreter.cc"
#endif

View File

@ -4,5 +4,6 @@
#include "parser/expr.hh"
#include "parser/printer.hh"
#include "parser/parser.hh"
#include "parser/interpreter.hh"
#endif

View File

@ -0,0 +1,168 @@
#include "interpreter.hh"
#include "expr.hh"
#include "../error_handler.hh"
#include "../tokenizer.hh"
#include <memory>
#include <string>
#include <cassert>
#include <iostream>
extern ErrorHandler error_handler;
void AstInterpreter::interpret(const Expr &expression) {
try {
Object value = evaluate(expression);
std::cout << value.to_string() << '\n';
} catch (RuntimeException e) {
error_handler.runtime_error(e);
}
}
Object AstInterpreter::evaluate(const Expr &expr) {
switch (expr.type) {
case ExprType::BINARY:
return evaluate_binary_expression(expr);
case ExprType::GROUPING:
return evaluate_grouping_expression(expr);
case ExprType::LITERAL:
return evaluate_literal_expression(expr);
case ExprType::UNARY:
return evaluate_unary_expression(expr);
}
}
Object AstInterpreter::evaluate_binary_expression(const Expr &expr) {
std::shared_ptr<_Binary> ptr = std::get<std::shared_ptr<_Binary>>(expr.value);
assert(ptr != nullptr);
Object left = evaluate(ptr->left);
Object right = evaluate(ptr->right);
if (left.type == ObjectType::NUMBER && right.type == ObjectType::NUMBER) {
double left_val = std::get<double>(left.value);
double right_val = std::get<double>(right.value);
switch (ptr->op.type) {
case TokenType::PLUS: return left_val + right_val;
case TokenType::MINUS: return left_val - right_val;
case TokenType::STAR: return left_val * right_val;
case TokenType::SLASH: {
if (right_val == 0) {
throw RuntimeException{ptr->op, "Division by zero."};
}
return left_val / right_val;
}
case TokenType::GREATER: return left_val > right_val;
case TokenType::GREATER_EQUAL: return left_val >= right_val;
case TokenType::LESS: return left_val < right_val;
case TokenType::LESS_EQUAL: return left_val <= right_val;
case TokenType::BANG_EQUAL: return left_val != right_val;
case TokenType::EQUAL_EQUAL: return left_val == right_val;
default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for numbers."};
}
}
if (left.type == ObjectType::STRING_LIT) {
right = Object{StringObjectType::LITERAL, right.to_string()};
}
if (right.type == ObjectType::STRING_LIT) {
left = Object{StringObjectType::LITERAL, left.to_string()};
}
if (left.type == ObjectType::STRING_LIT && right.type == ObjectType::STRING_LIT) {
std::string left_val = std::get<std::string>(left.value);
std::string right_val = std::get<std::string>(right.value);
switch (ptr->op.type) {
case TokenType::PLUS: return Object{StringObjectType::LITERAL, left_val + right_val};
case TokenType::BANG_EQUAL: return left_val != right_val;
case TokenType::EQUAL_EQUAL: return left_val == right_val;
default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for strings."};
}
}
if (left.type == ObjectType::BOOL && right.type == ObjectType::BOOL) {
bool left_val = std::get<bool>(left.value);
bool right_val = std::get<bool>(right.value);
switch (ptr->op.type) {
case TokenType::BANG_EQUAL: return left_val != right_val;
case TokenType::EQUAL_EQUAL: return left_val == right_val;
default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for booleans."};
}
}
if (left.type == ObjectType::NIL && right.type == ObjectType::NIL) {
switch (ptr->op.type) {
case TokenType::BANG_EQUAL: return false;
case TokenType::EQUAL_EQUAL: return true;
default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for null."};
}
}
if (left.type == ObjectType::NIL) {
switch (ptr->op.type) {
case TokenType::BANG_EQUAL: return true;
case TokenType::EQUAL_EQUAL: return false;
default: break;
}
}
throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for operands " + left.to_string() + " and " + right.to_string() + "."};
}
Object AstInterpreter::evaluate_grouping_expression(const Expr &expr) {
std::shared_ptr<_Grouping> ptr = std::get<std::shared_ptr<_Grouping>>(expr.value);
assert(ptr != nullptr);
return evaluate(ptr->expr);
}
Object AstInterpreter::evaluate_literal_expression(const Expr &expr) {
std::shared_ptr<_Literal> ptr = std::get<std::shared_ptr<_Literal>>(expr.value);
assert(ptr != nullptr);
return ptr->value;
}
Object AstInterpreter::evaluate_unary_expression(const Expr &expr) {
std::shared_ptr<_Unary> ptr = std::get<std::shared_ptr<_Unary>>(expr.value);
assert(ptr != nullptr);
Object right = evaluate(ptr->right);
switch (ptr->op.type) {
case TokenType::MINUS: {
switch (right.type) {
case ObjectType::NUMBER: {
double value = std::get<double>(right.value);
return -value;
}
default: throw RuntimeException{ptr->op, "Operand to unary operator - must be a number."};
}
}
case TokenType::BANG:
return !is_truthy(right);
default: break;
}
return Object{};
}
bool AstInterpreter::is_truthy(const Object &object) {
switch (object.type) {
case ObjectType::NIL: return false;
case ObjectType::BOOL: {
bool value = std::get<bool>(object.value);
return value;
}
default: return true;
}
}

View File

@ -0,0 +1,21 @@
#ifndef AST_INTERPRETER_HH
#define AST_INTERPRETER_HH
#include "expr.hh"
#include "../tokenizer.hh"
#include <string>
struct AstInterpreter {
void interpret(const Expr &expression);
private:
Object evaluate(const Expr &expr);
Object evaluate_binary_expression(const Expr &expr);
Object evaluate_grouping_expression(const Expr &expr);
Object evaluate_literal_expression(const Expr &expr);
Object evaluate_unary_expression(const Expr &expr);
bool is_truthy(const Object &object);
bool is_equal(const Object &left, const Object &right);
};
#endif

View File

@ -21,6 +21,35 @@ std::string Object::to_string() {
}
}
bool Object::operator==(const Object &other) {
if (type != other.type) {
return false;
}
switch (type) {
case ObjectType::NIL: return true;
case ObjectType::BOOL: {
bool left = std::get<bool>(value);
bool right = std::get<bool>(other.value);
return left == right;
}
case ObjectType::IDENTIFIER:
case ObjectType::STRING_LIT: {
std::string left = std::get<std::string>(value);
std::string right = std::get<std::string>(other.value);
return left == right;
}
case ObjectType::NUMBER: {
double left = std::get<double>(value);
double right = std::get<double>(other.value);
return left == right;
}
}
}
std::ostream &operator<<(std::ostream &os, const ObjectType &type) {
switch (type) {
case ObjectType::NIL:
@ -64,3 +93,7 @@ std::ostream &operator<<(std::ostream &os, const Object &obj) {
return os;
}
bool Object::operator!=(const Object &other) {
return !operator==(other);
}

View File

@ -30,6 +30,8 @@ struct Object {
},
value{std::move(value)} {};
std::string to_string();
bool operator==(const Object &other);
bool operator!=(const Object &other);
ObjectType type;
std::variant<std::monostate, std::string, double, bool> value;