Add expression evaluation
This commit is contained in:
		@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,5 +4,6 @@
 | 
			
		||||
#include "parser/expr.cc"
 | 
			
		||||
#include "parser/printer.cc"
 | 
			
		||||
#include "parser/parser.cc"
 | 
			
		||||
#include "parser/interpreter.cc"
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -4,5 +4,6 @@
 | 
			
		||||
#include "parser/expr.hh"
 | 
			
		||||
#include "parser/printer.hh"
 | 
			
		||||
#include "parser/parser.hh"
 | 
			
		||||
#include "parser/interpreter.hh"
 | 
			
		||||
 | 
			
		||||
#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) {
 | 
			
		||||
  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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user