Add expression evaluation
This commit is contained in:
parent
7b8903c19c
commit
e352df9d7d
@ -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) {
|
void ErrorHandler::report(int line, const std::string &where, const std::string &message) {
|
||||||
std::cout << "[line " << line << "] Error" << where << ": " << message << '\n';
|
std::cout << "[line " << line << "] Error" << where << ": " << message << '\n';
|
||||||
had_error = true;
|
had_error = true;
|
||||||
|
@ -2,15 +2,29 @@
|
|||||||
#define ERROR_HANDLER_HH
|
#define ERROR_HANDLER_HH
|
||||||
|
|
||||||
#include "tokenizer.hh"
|
#include "tokenizer.hh"
|
||||||
|
#include <exception>
|
||||||
#include <string>
|
#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 {
|
struct ErrorHandler {
|
||||||
ErrorHandler() : had_error{false} {};
|
ErrorHandler() : had_error{false} {};
|
||||||
void error(int line, const std::string &message);
|
void error(int line, const std::string &message);
|
||||||
void error(Token token, 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);
|
void report(int line, const std::string &where, const std::string &message);
|
||||||
|
|
||||||
bool had_error;
|
bool had_error;
|
||||||
|
bool had_runtime_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -35,6 +35,9 @@ void run_file(const char *path) {
|
|||||||
if (error_handler.had_error) {
|
if (error_handler.had_error) {
|
||||||
exit(EX_DATAERR);
|
exit(EX_DATAERR);
|
||||||
}
|
}
|
||||||
|
if (error_handler.had_runtime_error) {
|
||||||
|
exit(EX_SOFTWARE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +48,7 @@ void run_prompt() {
|
|||||||
run(line);
|
run(line);
|
||||||
std::cout << PROMPT;
|
std::cout << PROMPT;
|
||||||
error_handler.had_error = false;
|
error_handler.had_error = false;
|
||||||
|
error_handler.had_runtime_error = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +59,13 @@ void run(const std::string &code) {
|
|||||||
Parser parser{tokens};
|
Parser parser{tokens};
|
||||||
std::optional<Expr> expression = parser.parse();
|
std::optional<Expr> expression = parser.parse();
|
||||||
|
|
||||||
|
static AstInterpreter interpreter{};
|
||||||
|
|
||||||
|
if (error_handler.had_error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (expression) {
|
if (expression) {
|
||||||
AstPrinter printer{};
|
interpreter.interpret(*expression);
|
||||||
std::cout << printer.print(*expression) << '\n';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,6 @@
|
|||||||
#include "parser/expr.cc"
|
#include "parser/expr.cc"
|
||||||
#include "parser/printer.cc"
|
#include "parser/printer.cc"
|
||||||
#include "parser/parser.cc"
|
#include "parser/parser.cc"
|
||||||
|
#include "parser/interpreter.cc"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,5 +4,6 @@
|
|||||||
#include "parser/expr.hh"
|
#include "parser/expr.hh"
|
||||||
#include "parser/printer.hh"
|
#include "parser/printer.hh"
|
||||||
#include "parser/parser.hh"
|
#include "parser/parser.hh"
|
||||||
|
#include "parser/interpreter.hh"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
168
cclox_src/parser/interpreter.cc
Normal file
168
cclox_src/parser/interpreter.cc
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
21
cclox_src/parser/interpreter.hh
Normal file
21
cclox_src/parser/interpreter.hh
Normal 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
|
@ -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) {
|
std::ostream &operator<<(std::ostream &os, const ObjectType &type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ObjectType::NIL:
|
case ObjectType::NIL:
|
||||||
@ -64,3 +93,7 @@ std::ostream &operator<<(std::ostream &os, const Object &obj) {
|
|||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Object::operator!=(const Object &other) {
|
||||||
|
return !operator==(other);
|
||||||
|
}
|
||||||
|
@ -30,6 +30,8 @@ struct Object {
|
|||||||
},
|
},
|
||||||
value{std::move(value)} {};
|
value{std::move(value)} {};
|
||||||
std::string to_string();
|
std::string to_string();
|
||||||
|
bool operator==(const Object &other);
|
||||||
|
bool operator!=(const Object &other);
|
||||||
|
|
||||||
ObjectType type;
|
ObjectType type;
|
||||||
std::variant<std::monostate, std::string, double, bool> value;
|
std::variant<std::monostate, std::string, double, bool> value;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user