Compare commits

...

19 Commits

Author SHA1 Message Date
f094fcb376 Add block statements 2025-06-29 22:03:03 +01:00
fb06f6f483 Remove unnecessary code 2025-06-29 20:24:01 +01:00
4f39417d3e Start adding statements 2025-06-29 19:25:08 +01:00
Abdelrahman Said
2690c07220 Display integers with no decimal points 2025-06-29 12:37:07 +01:00
e4723166a2 Rename printer and interpreter to ast_printer and ast_interpreter 2025-06-29 02:18:25 +01:00
e352df9d7d Add expression evaluation 2025-06-29 02:15:10 +01:00
7b8903c19c Add Object.to_string method 2025-06-29 00:19:18 +01:00
b3291a9e3a Add asserts 2025-06-29 00:13:52 +01:00
3aec8b6b5b Reformat 2025-06-29 00:03:12 +01:00
1c1d754750 Finish parsing expressions 2025-06-28 22:51:02 +01:00
6f156c0487 Add bool type to object 2025-06-28 21:24:10 +01:00
1118e825f3 Switch to macros for better readability and add printer.hh 2025-06-28 18:27:00 +01:00
Abdelrahman Said
a6e129eb12 Update to reduce copying of expressions 2025-06-22 12:39:47 +01:00
Abdelrahman Said
c6f9307253 And that's why I hate C++ 2025-06-21 16:51:35 +01:00
Abdelrahman Said
339b92dbae Update .gitignore 2025-06-21 16:51:20 +01:00
Abdelrahman Said
a57cb5fde0 Update .gitignore 2025-06-21 13:11:31 +01:00
Abdelrahman Said
c78e694f87 Improved solution with inline functions 2025-06-20 23:25:12 +01:00
Abdelrahman Said
de85b0d93c More ergonomic solution using macros, but still needs improvement 2025-06-19 08:23:24 +01:00
56e259d7ed Slightly improved version of the parser 2025-06-18 22:57:52 +01:00
22 changed files with 1100 additions and 126 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
cclox cclox
clox clox
*.dSYM *.dSYM
.vscode
.zed

View File

@@ -13,7 +13,7 @@ CLOX_OUT = clox
all: cclox clox all: cclox clox
cclox: ${CCLOX_SRC} cclox: ${CCLOX_SRC}
${CXX} ${CFLAGS} ${CCLOX_SRC} -o ${CCLOX_OUT} ${CXX} -std=c++20 ${CFLAGS} ${CCLOX_SRC} -o ${CCLOX_OUT}
clox: ${CLOX_SRC} clox: ${CLOX_SRC}
${CC} ${CFLAGS} ${CLOX_SRC} -o ${CLOX_OUT} ${CC} ${CFLAGS} ${CLOX_SRC} -o ${CLOX_OUT}

View File

@@ -1,10 +1,24 @@
#include "error_handler.hh" #include "error_handler.hh"
#include "tokenizer.hh"
#include <iostream> #include <iostream>
void ErrorHandler::error(int line, const std::string &message) { void ErrorHandler::error(int line, const std::string &message) {
report(line, "", message); report(line, "", message);
} }
void ErrorHandler::error(Token token, const std::string &message) {
if (token.type == TokenType::EOFILE) {
report(token.line, " at end", message);
} else {
report(token.line, " at '" + token.lexeme + "'", 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;

View File

@@ -1,14 +1,30 @@
#ifndef ERROR_HANDLER_HH #ifndef ERROR_HANDLER_HH
#define ERROR_HANDLER_HH #define ERROR_HANDLER_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 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

View File

@@ -1,6 +1,8 @@
#include "interpreter.hh" #include "interpreter.hh"
#include "error_handler.hh" #include "error_handler.hh"
#include "tokenizer.hh" #include "tokenizer.hh"
#include "parser.hh"
#include <vector>
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@@ -33,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);
}
} }
} }
@@ -43,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;
} }
} }
@@ -50,7 +56,13 @@ void run(const std::string &code) {
Scanner scanner{code}; Scanner scanner{code};
std::vector<Token> tokens = scanner.scan_tokens(); std::vector<Token> tokens = scanner.scan_tokens();
for (const auto &token : tokens) { Parser parser{tokens};
std::cout << token << '\n'; std::vector<Stmt> statements = parser.parse();
if (error_handler.had_error) {
return;
} }
static AstInterpreter interpreter{};
interpreter.interpret(statements);
} }

View File

@@ -8,27 +8,12 @@
ErrorHandler error_handler{}; ErrorHandler error_handler{};
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void)argc; if (argc > 2) {
(void)argv; std::cout << "Usage: cclox [script]\n";
// if (argc > 2) { exit(EX_USAGE);
// std::cout << "Usage: cclox [script]\n"; }
// exit(EX_USAGE);
// }
//
// run_interpreter(argc, argv);
ExprPtr literal = std::make_shared<Expr>(ExprType::LITERAL, Literal{123}); run_interpreter(argc, argv);
ExprPtr left = std::make_shared<Expr>(ExprType::UNARY, Unary{Token{TokenType::MINUS, "-", Object{}, 1}, literal});
Token op{TokenType::STAR, "*", Object{}, 1};
ExprPtr gliteral = std::make_shared<Expr>(ExprType::LITERAL, Literal{45.67});
ExprPtr right = std::make_shared<Expr>(ExprType::GROUPING, gliteral);
Expr expr = {ExprType::BINARY, Binary{left, op, right}};
AstPrinter printer{};
std::cout << printer.print(expr) << '\n';
return 0; return 0;
} }

View File

@@ -1,6 +1,11 @@
#ifndef PARSER_CC #ifndef MAIN_PARSER_CC
#define PARSER_CC #define MAIN_PARSER_CC
#include "parser/expr.cc" #include "parser/expr.cc"
#include "parser/stmt.cc"
#include "parser/parser.cc"
#include "parser/environment.cc"
#include "parser/ast_printer.cc"
#include "parser/ast_interpreter.cc"
#endif #endif

View File

@@ -1,6 +1,11 @@
#ifndef PARSER_HH #ifndef MAIN_PARSER_HH
#define PARSER_HH #define MAIN_PARSER_HH
#include "parser/expr.hh" #include "parser/expr.hh"
#include "parser/stmt.hh"
#include "parser/parser.hh"
#include "parser/environment.hh"
#include "parser/ast_printer.hh"
#include "parser/ast_interpreter.hh"
#endif #endif

View File

@@ -0,0 +1,242 @@
#include "ast_interpreter.hh"
#include "environment.hh"
#include "expr.hh"
#include "stmt.hh"
#include "../error_handler.hh"
#include "../tokenizer.hh"
#include <exception>
#include <memory>
#include <string>
#include <vector>
#include <cassert>
#include <iostream>
extern ErrorHandler error_handler;
void AstInterpreter::interpret(const std::vector<Stmt> &statements) {
try {
for (const Stmt & stmt : statements) {
execute(stmt);
}
} catch (RuntimeException e) {
error_handler.runtime_error(e);
}
}
void AstInterpreter::execute(const Stmt &stmt) {
switch (stmt.type) {
case StmtType::EXPRESSION: return execute_expression_statement(stmt);
case StmtType::PRINT: return execute_print_statement(stmt);
case StmtType::VAR: return execute_var_statement(stmt);
case StmtType::BLOCK: {
std::shared_ptr<_Block> ptr = std::get<std::shared_ptr<_Block>>(stmt.value);
assert(ptr != nullptr);
return execute_block_statement(ptr->statements, Environment{std::make_shared<Environment>(environment)});
}
}
}
void AstInterpreter::execute_expression_statement(const Stmt &stmt) {
std::shared_ptr<_Expression> ptr = std::get<std::shared_ptr<_Expression>>(stmt.value);
assert(ptr != nullptr);
evaluate(ptr->expr);
}
void AstInterpreter::execute_print_statement(const Stmt &stmt) {
std::shared_ptr<_Print> ptr = std::get<std::shared_ptr<_Print>>(stmt.value);
assert(ptr != nullptr);
Object value{evaluate(ptr->expr)};
std::cout << value << '\n';
}
void AstInterpreter::execute_var_statement(const Stmt &stmt) {
Object value{};
std::shared_ptr<_Var> ptr = std::get<std::shared_ptr<_Var>>(stmt.value);
assert(ptr != nullptr);
if (ptr->expr.type == ExprType::LITERAL) {
std::shared_ptr<_Literal> eptr = std::get<std::shared_ptr<_Literal>>(ptr->expr.value);
assert(eptr != nullptr);
if (eptr->value.type == ObjectType::NIL) {
environment.define(ptr->name.lexeme, value);
return;
}
}
value = evaluate(ptr->expr);
environment.define(ptr->name.lexeme, value);
}
void AstInterpreter::execute_block_statement(const std::vector<Stmt> &statements, Environment environment) {
Environment previous = this->environment;
try {
this->environment = environment;
for (const auto &stmt : statements) {
execute(stmt);
}
} catch (std::exception) {}
this->environment = previous;
}
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);
case ExprType::VARIABLE: return evaluate_variable_expression(expr);
case ExprType::ASSIGN: return evaluate_assignment_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{};
}
Object AstInterpreter::evaluate_variable_expression(const Expr &expr) {
std::shared_ptr<_Variable> ptr = std::get<std::shared_ptr<_Variable>>(expr.value);
assert(ptr != nullptr);
return environment.get(ptr->name);
}
Object AstInterpreter::evaluate_assignment_expression(const Expr &expr) {
std::shared_ptr<_Assign> ptr = std::get<std::shared_ptr<_Assign>>(expr.value);
assert(ptr != nullptr);
Object value = evaluate(ptr->value);
environment.assign(ptr->name, value);
return value;
}
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,33 @@
#ifndef AST_INTERPRETER_HH
#define AST_INTERPRETER_HH
#include "expr.hh"
#include "stmt.hh"
#include "environment.hh"
#include "../tokenizer.hh"
#include <vector>
struct AstInterpreter {
void interpret(const std::vector<Stmt> &statements);
private:
void execute(const Stmt &stmt);
void execute_expression_statement(const Stmt &stmt);
void execute_print_statement(const Stmt &stmt);
void execute_var_statement(const Stmt &stmt);
void execute_block_statement(const std::vector<Stmt> &statements, Environment environment);
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);
Object evaluate_variable_expression(const Expr &expr);
Object evaluate_assignment_expression(const Expr &expr);
bool is_truthy(const Object &object);
bool is_equal(const Object &left, const Object &right);
Environment environment{};
};
#endif

View File

@@ -0,0 +1,51 @@
#include "ast_printer.hh"
#include "expr.hh"
#include <cassert>
#include <string>
#include <sstream>
std::string AstPrinter::print(const Expr &expr) {
switch (expr.type) {
case ExprType::BINARY: {
std::shared_ptr<_Binary> ptr = std::get<std::shared_ptr<_Binary>>(expr.value);
assert(ptr != nullptr);
return parenthesize(ptr->op.lexeme, {&ptr->left, &ptr->right});
}
case ExprType::GROUPING: {
std::shared_ptr<_Grouping> ptr = std::get<std::shared_ptr<_Grouping>>(expr.value);
assert(ptr != nullptr);
return parenthesize("group", {&ptr->expr});
}
case ExprType::LITERAL: {
std::shared_ptr<_Literal> ptr = std::get<std::shared_ptr<_Literal>>(expr.value);
assert(ptr != nullptr);
return ptr->value.to_string();
}
case ExprType::UNARY: {
std::shared_ptr<_Unary> ptr = std::get<std::shared_ptr<_Unary>>(expr.value);
assert(ptr != nullptr);
return parenthesize(ptr->op.lexeme, {&ptr->right});
}
case ExprType::VARIABLE: {
std::shared_ptr<_Variable> ptr = std::get<std::shared_ptr<_Variable>>(expr.value);
assert(ptr != nullptr);
return ptr->name.lexeme;
}
case ExprType::ASSIGN: {
std::shared_ptr<_Assign> ptr = std::get<std::shared_ptr<_Assign>>(expr.value);
assert(ptr != nullptr);
return ptr->name.lexeme;
}
}
}
std::string AstPrinter::parenthesize(const std::string &name, std::vector<const Expr *> exprs) {
std::stringstream ss{};
ss << '(' << name;
for (const Expr *expr : exprs) {
ss << ' ' << print(*expr);
}
ss << ')';
return ss.str();
}

View File

@@ -0,0 +1,13 @@
#ifndef AST_PRINTER_HH
#define AST_PRINTER_HH
#include "expr.hh"
#include <string>
struct AstPrinter {
std::string print(const Expr &expr);
private:
std::string parenthesize(const std::string &name, std::vector<const Expr *> exprs);
};
#endif

View File

@@ -0,0 +1,64 @@
#include "environment.hh"
#include "../error_handler.hh"
#include <algorithm>
#include <memory>
#include <utility>
Environment::Environment(const Environment &other) : enclosing{other.enclosing}, values{other.values} {}
Environment::Environment(Environment &&other) noexcept
: enclosing{std::move(other.enclosing)}, values{std::move(other.values)} {}
Environment &Environment::operator=(const Environment &other) {
if (this == &other) {
return *this;
}
enclosing = other.enclosing;
values = other.values;
return *this;
}
Environment &Environment::operator=(Environment &&other) noexcept {
if (this == &other) {
return *this;
}
enclosing = std::move(other.enclosing);
values = std::move(other.values);
other.enclosing.reset();
return *this;
}
void Environment::define(const std::string &name, const Object &value) {
values.insert(std::pair(name, value));
}
void Environment::assign(const Token &name, const Object &value) {
if (values.find(name.lexeme) != values.end()) {
values.at(name.lexeme) = value;
return;
}
if (enclosing != nullptr) {
enclosing->assign(name, value);
return;
}
throw RuntimeException(name, "Undefined variable '" + name.lexeme + "'.");
}
Object Environment::get(const Token &name) {
if (values.find(name.lexeme) != values.end()) {
return values.at(name.lexeme);
}
if (enclosing != nullptr) {
return enclosing->get(name);
}
throw RuntimeException(name, "Undefined variable '" + name.lexeme + "'.");
}

View File

@@ -0,0 +1,28 @@
#ifndef ENVIRONMENT_HH
#define ENVIRONMENT_HH
#include "../tokenizer.hh"
#include <memory>
#include <string>
#include <unordered_map>
struct Environment {
Environment() : enclosing{nullptr}, values{} {}
Environment(std::shared_ptr<Environment> enclosing) : enclosing{enclosing}, values{} {}
Environment(const Environment &other);
Environment(Environment &&other) noexcept;
Environment &operator=(const Environment &other);
Environment &operator=(Environment &&other) noexcept;
~Environment() = default;
void define(const std::string &name, const Object &value);
void assign(const Token &name, const Object &value);
Object get(const Token &name);
private:
std::shared_ptr<Environment> enclosing;
std::unordered_map<std::string, Object> values;
};
#endif

View File

@@ -1,53 +1,35 @@
#include "expr.hh" #include "expr.hh"
#include "../tokenizer.hh" #include "../tokenizer.hh"
#include <cassert> #include <utility>
#include <string>
#include <vector>
#include <sstream>
std::string AstPrinter::print(const Expr &expr) { Expr::Expr(ExprType type, ExprVariant value) : type{type}, value{value} {}
switch (expr.type) {
case ExprType::BINARY: { // Copy constructor
Binary node = std::get<Binary>(expr.value); Expr::Expr(const Expr &other) : type{other.type}, value{other.value} {}
assert(node.left != NULL && node.right != NULL);
return parenthesize(node.op.lexeme, {*node.left, *node.right}); // Move constructor
} Expr::Expr(Expr &&other) noexcept : type{other.type}, value{std::move(other.value)} {}
case ExprType::GROUPING: {
Grouping node = std::get<Grouping>(expr.value); // Copy assignment
assert(node.expression != NULL); Expr &Expr::operator=(const Expr &other) {
return parenthesize("group", {*node.expression}); if (this == &other) {
} return *this;
case ExprType::LITERAL: {
Literal node = std::get<Literal>(expr.value);
switch (node.value.type) {
case ObjectType::NIL:
return "nil";
case ObjectType::IDENTIFIER:
case ObjectType::STRING_LIT: {
std::string value = std::get<std::string>(node.value.value);
return value;
}
case ObjectType::NUMBER: {
double value = std::get<double>(node.value.value);
return std::to_string(value);
}
}
}
case ExprType::UNARY: {
Unary node = std::get<Unary>(expr.value);
assert(node.right != NULL);
return parenthesize(node.op.lexeme, {*node.right});
}
} }
type = other.type;
value = other.value;
return *this;
} }
std::string AstPrinter::parenthesize(const std::string &name, std::vector<Expr> exprs) { // Move assignment
std::stringstream ss{}; Expr &Expr::operator=(Expr &&other) noexcept {
ss << '(' << name; if (this == &other) {
for (auto &expr : exprs) { return *this;
ss << ' ' << print(expr);
} }
ss << ')';
return ss.str(); type = std::move(other.type);
value = std::move(other.value);
return *this;
} }

View File

@@ -3,60 +3,94 @@
#include "../tokenizer.hh" #include "../tokenizer.hh"
#include <memory> #include <memory>
#include <string>
#include <variant> #include <variant>
struct Expr; struct Expr;
struct _Binary;
using ExprPtr = std::shared_ptr<Expr>; struct _Grouping;
struct _Literal;
struct Binary { struct _Unary;
Binary(ExprPtr left, Token op, ExprPtr right) : left{left}, op{op}, right{right} {}; struct _Variable;
struct _Assign;
ExprPtr left;
Token op;
ExprPtr right;
};
struct Grouping {
Grouping(ExprPtr expr) : expression{expr} {};
ExprPtr expression;
};
struct Literal {
Literal(Object value) : value{value} {};
Object value;
};
struct Unary {
Unary(Token op, ExprPtr right) : op{op}, right{right} {};
Token op;
ExprPtr right;
};
enum class ExprType { enum class ExprType {
BINARY, BINARY,
GROUPING, GROUPING,
LITERAL, LITERAL,
UNARY, UNARY,
VARIABLE,
ASSIGN,
}; };
using Expression = std::variant<Binary, Grouping, Literal, Unary>; using ExprVariant = std::variant<
std::shared_ptr<_Binary>,
std::shared_ptr<_Grouping>,
std::shared_ptr<_Literal>,
std::shared_ptr<_Unary>,
std::shared_ptr<_Variable>,
std::shared_ptr<_Assign>
>;
#define Binary(LEFT, OP, RIGHT) (Expr{ExprType::BINARY, std::make_shared<_Binary>(LEFT, OP, RIGHT)})
#define Grouping(EXPR) (Expr{ExprType::GROUPING, std::make_shared<_Grouping>(EXPR)})
#define Literal(VALUE) (Expr{ExprType::LITERAL, std::make_shared<_Literal>(VALUE)})
#define Unary(OP, RIGHT) (Expr{ExprType::UNARY, std::make_shared<_Unary>(OP, RIGHT)})
#define Variable(NAME) (Expr{ExprType::VARIABLE, std::make_shared<_Variable>(NAME)})
#define Assign(NAME, VALUE) (Expr{ExprType::ASSIGN, std::make_shared<_Assign>(NAME, VALUE)})
struct Expr { struct Expr {
Expr(ExprType type, Expression value) : type{type}, value{value} {} Expr(ExprType type, ExprVariant value);
// Copy constructor
Expr(const Expr &other);
// Move constructor
Expr(Expr &&other) noexcept;
// Copy assignment
Expr &operator=(const Expr &other);
// Move assignment
Expr &operator=(Expr &&other) noexcept;
~Expr() = default;
ExprType type; ExprType type;
Expression value; ExprVariant value;
}; };
class AstPrinter { struct _Binary {
public: _Binary(const Expr &left, Token op, const Expr &right) : left{left}, op{op}, right{right} {}
std::string print(const Expr &expr); Expr left;
private: Token op;
std::string parenthesize(const std::string &name, std::vector<Expr> exprs); Expr right;
};
struct _Grouping {
_Grouping(const Expr &expr) : expr{expr} {}
Expr expr;
};
struct _Literal {
_Literal(Object value) : value{value} {}
Object value;
};
struct _Unary {
_Unary(Token op, const Expr &right) : op{op}, right{right} {}
Token op;
Expr right;
};
struct _Variable {
_Variable(Token name) : name{name} {}
Token name;
};
struct _Assign {
_Assign(Token name, const Expr &value) : name{name}, value{value} {}
Token name;
Expr value;
}; };
#endif #endif

272
cclox_src/parser/parser.cc Normal file
View File

@@ -0,0 +1,272 @@
#include "parser.hh"
#include "expr.hh"
#include "stmt.hh"
#include "../error_handler.hh"
#include "../tokenizer.hh"
#include <cassert>
extern ErrorHandler error_handler;
std::vector<Stmt> Parser::parse() {
std::vector<Stmt> statements{};
while (!is_at_end()) {
statements.push_back(declaration());
}
return statements;
}
Stmt Parser::declaration() {
try {
if (match({TokenType::VAR})) {
return var_declaration();
}
return statement();
} catch (ParseException e) {
synchronize();
return Expression(Literal(Object{}));
}
}
Stmt Parser::var_declaration() {
Token name = consume(TokenType::IDENTIFIER, "Expect variable name.");
Expr initializer = Literal(Object{});
if (match({TokenType::EQUAL})) {
initializer = expression();
}
consume(TokenType::SEMICOLON, "Expect ';' after variable declaration");
return Var(name, initializer);
}
Stmt Parser::statement() {
if (match({TokenType::PRINT})) {
return print_statement();
}
if (match({TokenType::LEFT_BRACE})) {
return Block(block_statement());
}
return expression_statement();
}
std::vector<Stmt> Parser::block_statement() {
std::vector<Stmt> statements{};
while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) {
statements.push_back(declaration());
}
consume(TokenType::RIGHT_BRACE, "Expect '}' after block.");
return statements;
}
Stmt Parser::print_statement() {
Expr value{expression()};
consume(TokenType::SEMICOLON, "Expect ';' after value.");
return Print(value);
}
Stmt Parser::expression_statement() {
Expr value{expression()};
consume(TokenType::SEMICOLON, "Expect ';' after value.");
return Expression(value);
}
Expr Parser::expression() {
return assignment();
}
Expr Parser::assignment() {
Expr expr = equality();
if (match({TokenType::EQUAL})) {
Token equals = previous();
Expr value = assignment();
if (expr.type == ExprType::VARIABLE) {
std::shared_ptr<_Variable> ptr = std::get<std::shared_ptr<_Variable>>(expr.value);
assert(ptr != nullptr);
Token name = ptr->name;
return Assign(name, value);
}
error_handler.error(equals, "Invalid assignment target.");
}
return expr;
}
Expr Parser::equality() {
Expr expr = comparison();
while (match({TokenType::BANG_EQUAL, TokenType::EQUAL_EQUAL})) {
Token op = previous();
Expr right = comparison();
expr = Binary(expr, op, right);
}
return expr;
}
Expr Parser::comparison() {
Expr expr = term();
while (match({TokenType::GREATER, TokenType::GREATER_EQUAL, TokenType::LESS, TokenType::LESS_EQUAL})) {
Token op = previous();
Expr right = term();
expr = Binary(expr, op, right);
}
return expr;
}
Expr Parser::term() {
Expr expr = factor();
while (match({TokenType::MINUS, TokenType::PLUS})) {
Token op = previous();
Expr right = factor();
expr = Binary(expr, op, right);
}
return expr;
}
Expr Parser::factor() {
Expr expr = unary();
while (match({TokenType::SLASH, TokenType::STAR})) {
Token op = previous();
Expr right = unary();
expr = Binary(expr, op, right);
}
return expr;
}
Expr Parser::unary() {
if (match({TokenType::BANG, TokenType::MINUS})) {
Token op = previous();
Expr right = unary();
return Unary(op, right);
}
return primary();
}
Expr Parser::primary() {
if (match({TokenType::FALSE})) {
return Literal(false);
}
if (match({TokenType::TRUE})) {
return Literal(true);
}
if (match({TokenType::NIL})) {
return Literal(Object{});
}
if (match({TokenType::NUMBER, TokenType::STRING})) {
return Literal(previous().literal);
}
if (match({TokenType::IDENTIFIER})) {
return Variable(previous());
}
if (match({TokenType::LEFT_PAREN})) {
Expr expr = expression();
consume(TokenType::RIGHT_PAREN, "Expect ')' after expression.");
return Grouping(expr);
}
throw error(peek(), "Expect expression.");
}
Token Parser::advance() {
if (!is_at_end()) {
++current;
}
return previous();
}
Token Parser::peek() {
return tokens.at(current);
}
Token Parser::previous() {
return tokens.at(current - 1);
}
Token Parser::consume(TokenType type, std::string &&message) {
if (check(type)) {
return advance();
}
throw error(peek(), message);
}
bool Parser::match(std::vector<TokenType> types) {
for (const auto &type : types) {
if (check(type)) {
advance();
return true;
}
}
return false;
}
bool Parser::check(TokenType type) {
if (is_at_end()) {
return false;
}
return peek().type == type;
}
bool Parser::is_at_end() {
return peek().type == TokenType::EOFILE;
}
ParseException Parser::error(Token token, std::string &message) {
error_handler.error(token, message);
return ParseException{};
}
ParseException Parser::error(Token token, std::string &&message) {
return error(token, message);
}
void Parser::synchronize() {
advance();
while (!is_at_end()) {
if (previous().type == TokenType::SEMICOLON) {
return;
}
switch (peek().type) {
case TokenType::CLASS:
case TokenType::FUN:
case TokenType::VAR:
case TokenType::FOR:
case TokenType::IF:
case TokenType::WHILE:
case TokenType::PRINT:
case TokenType::RETURN:
return;
default:
break;
}
advance();
}
}

View File

@@ -0,0 +1,54 @@
#ifndef PARSER_HH
#define PARSER_HH
#include "expr.hh"
#include "stmt.hh"
#include "../tokenizer.hh"
#include <cstddef>
#include <exception>
#include <vector>
struct ParseException : public std::exception {
ParseException() : message{""} {}
ParseException(const std::string& message) : message{message} {}
const char* what() const noexcept override {
return message.c_str();
}
private:
std::string message;
};
struct Parser {
Parser(std::vector<Token> tokens) : tokens{tokens} {}
std::vector<Stmt> parse();
Stmt declaration();
Stmt var_declaration();
Stmt statement();
std::vector<Stmt> block_statement();
Stmt print_statement();
Stmt expression_statement();
Expr expression();
Expr assignment();
Expr equality();
Expr comparison();
Expr term();
Expr factor();
Expr unary();
Expr primary();
Token advance();
Token peek();
Token previous();
Token consume(TokenType type, std::string &&message);
bool match(std::vector<TokenType> types);
bool check(TokenType type);
bool is_at_end();
ParseException error(Token token, std::string &message);
ParseException error(Token token, std::string &&message);
void synchronize();
std::vector<Token> tokens;
size_t current = 0;
};
#endif

35
cclox_src/parser/stmt.cc Normal file
View File

@@ -0,0 +1,35 @@
#include "stmt.hh"
#include <cassert>
#include <utility>
Stmt::Stmt(StmtType type, StmtVariant value) : type{type}, value{value} {}
// Copy constructor
Stmt::Stmt(const Stmt &other) : type{other.type}, value{other.value} {}
// Move constructor
Stmt::Stmt(Stmt &&other) noexcept : type{other.type}, value{std::move(other.value)} {}
// Copy assignment
Stmt &Stmt::operator=(const Stmt &other) {
if (this == &other) {
return *this;
}
type = other.type;
value = other.value;
return *this;
}
// Move assignment
Stmt &Stmt::operator=(Stmt &&other) noexcept {
if (this == &other) {
return *this;
}
type = std::move(other.type);
value = std::move(other.value);
return *this;
}

76
cclox_src/parser/stmt.hh Normal file
View File

@@ -0,0 +1,76 @@
#ifndef STMT_HH
#define STMT_HH
#include "expr.hh"
#include <memory>
#include <variant>
#include <vector>
struct Stmt;
struct _Expression;
struct _Print;
struct _Var;
struct _Block;
enum class StmtType {
EXPRESSION,
PRINT,
VAR,
BLOCK,
};
using StmtVariant = std::variant<
std::shared_ptr<_Expression>,
std::shared_ptr<_Print>,
std::shared_ptr<_Var>,
std::shared_ptr<_Block>
>;
#define Expression(EXPR) (Stmt{StmtType::EXPRESSION, std::make_shared<_Expression>(EXPR)})
#define Print(EXPR) (Stmt{StmtType::PRINT, std::make_shared<_Print>(EXPR)})
#define Var(NAME, EXPR) (Stmt{StmtType::VAR, std::make_shared<_Var>(NAME, EXPR)})
#define Block(STATEMENTS) (Stmt{StmtType::BLOCK, std::make_shared<_Block>(STATEMENTS)})
struct Stmt {
Stmt(StmtType type, StmtVariant value);
// Copy constructor
Stmt(const Stmt &other);
// Move constructor
Stmt(Stmt &&other) noexcept;
// Copy assignment
Stmt &operator=(const Stmt &other);
// Move assignment
Stmt &operator=(Stmt &&other) noexcept;
~Stmt() = default;
StmtType type;
StmtVariant value;
};
struct _Expression {
_Expression(const Expr &expr) : expr{expr} {}
Expr expr;
};
struct _Print {
_Print(const Expr &expr) : expr{expr} {}
Expr expr;
};
struct _Var {
_Var(const Token &name, const Expr &expr) : name{name}, expr{expr} {}
Token name;
Expr expr;
};
struct _Block {
_Block(const std::vector<Stmt> &statements) : statements{statements} {}
std::vector<Stmt> statements;
};
#endif

View File

@@ -1,11 +1,68 @@
#include "object.hh" #include "object.hh"
#include <cstddef>
#include <string> #include <string>
std::string Object::to_string() const {
switch (type) {
case ObjectType::NIL:
return "nil";
case ObjectType::BOOL: {
bool val = std::get<bool>(value);
return (val ? "true" : "false");
}
case ObjectType::IDENTIFIER:
case ObjectType::STRING_LIT: {
std::string val = std::get<std::string>(value);
return val;
}
case ObjectType::NUMBER: {
double val = std::get<double>(value);
std::string output{std::to_string(val)};
if (val == ((int)val)) {
size_t decimal_index{output.find(".")};
output = output.substr(0, decimal_index);
}
return output;
}
}
}
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:
os << "ObjectType::NIL"; os << "ObjectType::NIL";
break; break;
case ObjectType::BOOL:
os << "ObjectType::BOOL";
case ObjectType::IDENTIFIER: case ObjectType::IDENTIFIER:
os << "ObjectType::IDENTIFIER"; os << "ObjectType::IDENTIFIER";
break; break;
@@ -21,21 +78,10 @@ std::ostream &operator<<(std::ostream &os, const ObjectType &type) {
} }
std::ostream &operator<<(std::ostream &os, const Object &obj) { std::ostream &operator<<(std::ostream &os, const Object &obj) {
os << "Object(" << obj.type; os << obj.to_string();
switch (obj.type) {
case ObjectType::NIL:
break;
case ObjectType::IDENTIFIER:
case ObjectType::STRING_LIT:
os << ", " << std::get<std::string>(obj.value);
break;
case ObjectType::NUMBER:
os << ", " << std::get<double>(obj.value);
break;
}
os << ')';
return os; return os;
} }
bool Object::operator!=(const Object &other) {
return !operator==(other);
}

View File

@@ -12,6 +12,7 @@ enum class StringObjectType : uint8_t {
enum class ObjectType : uint8_t { enum class ObjectType : uint8_t {
NIL, NIL,
BOOL,
IDENTIFIER, IDENTIFIER,
STRING_LIT, STRING_LIT,
NUMBER, NUMBER,
@@ -19,6 +20,7 @@ enum class ObjectType : uint8_t {
struct Object { struct Object {
Object() : type{ObjectType::NIL}, value{std::monostate{}} {}; Object() : type{ObjectType::NIL}, value{std::monostate{}} {};
Object(bool value) : type{ObjectType::BOOL}, value{value} {};
Object(double number) : type{ObjectType::NUMBER}, value{number} {}; Object(double number) : type{ObjectType::NUMBER}, value{number} {};
Object(StringObjectType string_type, std::string value) Object(StringObjectType string_type, std::string value)
: type{ : type{
@@ -27,9 +29,12 @@ struct Object {
ObjectType::STRING_LIT ObjectType::STRING_LIT
}, },
value{std::move(value)} {}; value{std::move(value)} {};
std::string to_string() const;
bool operator==(const Object &other);
bool operator!=(const Object &other);
ObjectType type; ObjectType type;
std::variant<std::monostate, std::string, double> value; std::variant<std::monostate, std::string, double, bool> value;
}; };
std::ostream &operator<<(std::ostream &os, const ObjectType &type); std::ostream &operator<<(std::ostream &os, const ObjectType &type);