Compare commits
19 Commits
ae87a3b4a3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f094fcb376 | |||
| fb06f6f483 | |||
| 4f39417d3e | |||
|
|
2690c07220 | ||
| e4723166a2 | |||
| e352df9d7d | |||
| 7b8903c19c | |||
| b3291a9e3a | |||
| 3aec8b6b5b | |||
| 1c1d754750 | |||
| 6f156c0487 | |||
| 1118e825f3 | |||
|
|
a6e129eb12 | ||
|
|
c6f9307253 | ||
|
|
339b92dbae | ||
|
|
a57cb5fde0 | ||
|
|
c78e694f87 | ||
|
|
de85b0d93c | ||
| 56e259d7ed |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
cclox
|
cclox
|
||||||
clox
|
clox
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
.vscode
|
||||||
|
.zed
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
242
cclox_src/parser/ast_interpreter.cc
Normal file
242
cclox_src/parser/ast_interpreter.cc
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
cclox_src/parser/ast_interpreter.hh
Normal file
33
cclox_src/parser/ast_interpreter.hh
Normal 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
|
||||||
51
cclox_src/parser/ast_printer.cc
Normal file
51
cclox_src/parser/ast_printer.cc
Normal 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();
|
||||||
|
}
|
||||||
13
cclox_src/parser/ast_printer.hh
Normal file
13
cclox_src/parser/ast_printer.hh
Normal 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
|
||||||
64
cclox_src/parser/environment.cc
Normal file
64
cclox_src/parser/environment.cc
Normal 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 + "'.");
|
||||||
|
}
|
||||||
28
cclox_src/parser/environment.hh
Normal file
28
cclox_src/parser/environment.hh
Normal 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
|
||||||
@@ -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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AstPrinter::parenthesize(const std::string &name, std::vector<Expr> exprs) {
|
type = other.type;
|
||||||
std::stringstream ss{};
|
value = other.value;
|
||||||
ss << '(' << name;
|
|
||||||
for (auto &expr : exprs) {
|
|
||||||
ss << ' ' << print(expr);
|
|
||||||
}
|
|
||||||
ss << ')';
|
|
||||||
|
|
||||||
return ss.str();
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move assignment
|
||||||
|
Expr &Expr::operator=(Expr &&other) noexcept {
|
||||||
|
if (this == &other) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = std::move(other.type);
|
||||||
|
value = std::move(other.value);
|
||||||
|
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
272
cclox_src/parser/parser.cc
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
cclox_src/parser/parser.hh
Normal file
54
cclox_src/parser/parser.hh
Normal 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
35
cclox_src/parser/stmt.cc
Normal 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
76
cclox_src/parser/stmt.hh
Normal 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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user