Compare commits

..

1 Commits

Author SHA1 Message Date
Karma Riuk
bed99e0d63 added some sort of error generation when parsing
errors occur
2025-07-07 15:02:06 +02:00
62 changed files with 317 additions and 2568 deletions

View File

@@ -3,7 +3,6 @@
# ------------------------------------------------------------------- # -------------------------------------------------------------------
CXX := g++ CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -Iinclude -Isrc -MMD -MP CXXFLAGS := -std=c++17 -Wall -Wextra -Iinclude -Isrc -MMD -MP
CXXFLAGS_TESTS := -Itest/utils
LDFLAGS := LDFLAGS :=
SRC_DIR := src SRC_DIR := src
TEST_DIR := test TEST_DIR := test
@@ -49,7 +48,7 @@ run: $(TARGET)
@$(TARGET) @$(TARGET)
test: $(TEST_TARGET) test: $(TEST_TARGET)
@$(TEST_TARGET) $(if $(TEST),--test-case="$(TEST)") $(if $(SUBCASE),--subcase="$(SUBCASE)") @$(TEST_TARGET) $(if $(TEST),--test-case=$(TEST))
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Link binaries # Link binaries
@@ -71,7 +70,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(OBJ_DIR)/test/%.o: $(TEST_DIR)/%.cpp $(OBJ_DIR)/test/%.o: $(TEST_DIR)/%.cpp
@mkdir -p $(@D) @mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(CXXFLAGS_TESTS) -c $< -o $@ $(CXX) $(CXXFLAGS) -c $< -o $@
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Autoinclude dependencies # Autoinclude dependencies

View File

@@ -1,3 +1,2 @@
-I./include -I./include
-I./src -I./src
-I./test/utils

9
src/ast/ast.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "ast.hpp"
namespace ast {
std::string program ::token_literal() const {
if (statements.size() > 0)
return statements[0]->token_literal();
return "";
}
} // namespace ast

View File

@@ -1,20 +1,29 @@
#pragma once #pragma once
#include <iostream>
#include <string> #include <string>
#include <vector>
namespace ast { namespace ast {
struct node { struct node {
virtual std::string token_literal() const = 0; virtual std::string token_literal() const = 0;
virtual std::string str() const = 0;
virtual ~node() = default; virtual ~node() = default;
}; };
inline std::ostream& operator<<(std::ostream& os, const node& n) { struct statement : node {
return os << n.str(); virtual std::string token_literal() const override = 0;
} };
struct statement : node {}; struct expression : node {
virtual std::string token_literal() const override = 0;
};
struct expression : node {}; struct program : public node {
std::vector<statement*> statements;
std::string token_literal() const override;
~program() {
for (const auto& ref : statements)
delete ref;
};
};
} // namespace ast } // namespace ast

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "token/token.hpp"
#include "token/type.hpp" #include "token/type.hpp"
namespace ast::error { namespace ast::error {
@@ -13,14 +12,6 @@ namespace ast::error {
explicit parser_error(const std::string& message): error(message) {} explicit parser_error(const std::string& message): error(message) {}
}; };
struct unkown_prefix : parser_error {
token::token prefix;
explicit unkown_prefix(token::token prefix, const std::string& message)
: parser_error(message),
prefix(prefix) {}
};
struct expected_next : parser_error { struct expected_next : parser_error {
token::type expected_type; token::type expected_type;

View File

@@ -1,15 +0,0 @@
#include "boolean.hpp"
namespace ast {
boolean_literal::boolean_literal(token::token token, bool value)
: token(std::move(token)),
value(value) {}
std::string boolean_literal::token_literal() const {
return token.literal;
}
std::string boolean_literal::str() const {
return token.literal;
};
} // namespace ast

View File

@@ -1,17 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
#include <string>
namespace ast {
struct boolean_literal : expression {
boolean_literal(token::token, bool);
token::token token;
bool value;
std::string token_literal() const override;
std::string str() const override;
};
} // namespace ast

View File

@@ -1,66 +0,0 @@
#include "function.hpp"
#include <sstream>
namespace ast {
function_literal::function_literal(token::token token)
: token(std::move(token)),
body(nullptr) {};
std::string function_literal::token_literal() const {
return token.literal;
};
std::string function_literal::str() const {
std::stringstream ss;
ss << "fn (";
bool first = true;
for (auto& param : parameters) {
if (!first)
ss << ", ";
ss << param->str();
first = false;
}
ss << ")";
ss << body->str();
return ss.str();
};
function_literal::~function_literal() {
for (auto& param : parameters)
delete param;
if (body != nullptr)
delete body;
}
// ---------------------------- FUNCTION CALL ----------------------------
function_call::function_call(token::token token, expression* target)
: token(std::move(token)),
target(target) {};
std::string function_call::token_literal() const {
return token.literal;
};
std::string function_call::str() const {
std::stringstream ss;
ss << target->str() << "(";
bool first = true;
for (auto& param : parameters) {
if (!first)
ss << ", ";
ss << param->str();
first = false;
}
ss << ")";
return ss.str();
};
function_call::~function_call() {
if (target != nullptr)
delete target;
for (auto& param : parameters)
delete param;
}
} // namespace ast

View File

@@ -1,32 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "ast/expressions/identifier.hpp"
#include "ast/statements/block.hpp"
#include "token/token.hpp"
#include <string>
namespace ast {
struct function_literal : expression {
function_literal(token::token);
token::token token;
std::vector<identifier*> parameters;
ast::block_stmt* body;
std::string token_literal() const override;
std::string str() const override;
~function_literal();
};
struct function_call : expression {
function_call(token::token, expression*);
token::token token;
expression* target;
std::vector<expression*> parameters;
std::string token_literal() const override;
std::string str() const override;
~function_call();
};
} // namespace ast

View File

@@ -8,8 +8,4 @@ namespace ast {
std::string identifier::token_literal() const { std::string identifier::token_literal() const {
return token.literal; return token.literal;
} }
std::string identifier::str() const {
return value;
};
} // namespace ast } // namespace ast

View File

@@ -12,6 +12,5 @@ namespace ast {
std::string value; std::string value;
std::string token_literal() const override; std::string token_literal() const override;
std::string str() const override;
}; };
} // namespace ast } // namespace ast

View File

@@ -1,32 +0,0 @@
#include "if_then_else.hpp"
#include <sstream>
namespace ast {
if_then_else::if_then_else(token::token token)
: token(std::move(token)),
condition(nullptr),
consequence(nullptr),
alternative(nullptr) {};
std::string if_then_else::token_literal() const {
return token.literal;
};
std::string if_then_else::str() const {
std::stringstream ss;
ss << "if " << condition->str() << consequence->str();
if (alternative != nullptr)
ss << "else" << alternative->str();
return ss.str();
};
if_then_else::~if_then_else() {
if (condition != nullptr)
delete condition;
if (consequence != nullptr)
delete consequence;
if (alternative != nullptr)
delete alternative;
}
} // namespace ast

View File

@@ -1,20 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "ast/statements/block.hpp"
#include "token/token.hpp"
#include <string>
namespace ast {
struct if_then_else : expression {
if_then_else(token::token);
token::token token;
ast::expression* condition;
ast::block_stmt *consequence, *alternative;
std::string token_literal() const override;
std::string str() const override;
~if_then_else();
};
} // namespace ast

View File

@@ -1,26 +0,0 @@
#include "infix.hpp"
#include "token/token.hpp"
namespace ast {
infix_expr::infix_expr(
token::token token, std::string op, ast::expression* left
)
: token(std::move(token)),
op(op),
left(left) {}
std::string infix_expr::token_literal() const {
return token.literal;
}
std::string infix_expr::str() const {
return "(" + left->str() + " " + op + " " + right->str() + ")";
}
infix_expr::~infix_expr() {
delete left;
delete right;
};
} // namespace ast

View File

@@ -1,18 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
namespace ast {
struct infix_expr : expression {
infix_expr(token::token, std::string, ast::expression*);
token::token token;
std::string op;
expression* left;
expression* right;
std::string token_literal() const override;
std::string str() const override;
~infix_expr();
};
} // namespace ast

View File

@@ -1,15 +0,0 @@
#include "integer.hpp"
namespace ast {
integer_literal::integer_literal(token::token token, int value)
: token(std::move(token)),
value(value) {}
std::string integer_literal::token_literal() const {
return token.literal;
};
std::string integer_literal::str() const {
return token.literal;
};
} // namespace ast

View File

@@ -1,15 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
namespace ast {
struct integer_literal : expression {
integer_literal(token::token token, int value);
token::token token;
int value;
std::string token_literal() const override;
std::string str() const override;
};
} // namespace ast

View File

@@ -1,23 +0,0 @@
#include "prefix.hpp"
#include "token/token.hpp"
namespace ast {
prefix_expr::prefix_expr(token::token token, std::string op)
: token(std::move(token)),
op(op),
right(nullptr) {}
std::string prefix_expr::token_literal() const {
return token.literal;
}
std::string prefix_expr::str() const {
return "(" + op + right->str() + ")";
}
prefix_expr::~prefix_expr() {
delete right;
};
} // namespace ast

View File

@@ -1,17 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
namespace ast {
struct prefix_expr : expression {
prefix_expr(token::token token, std::string op);
token::token token;
std::string op;
expression* right;
std::string token_literal() const override;
std::string str() const override;
~prefix_expr();
};
} // namespace ast

View File

@@ -1,24 +0,0 @@
#include "program.hpp"
#include <sstream>
namespace ast {
std::string program ::token_literal() const {
if (statements.size() > 0)
return statements[0]->token_literal();
return "";
}
program::~program() {
for (const auto& ref : statements)
delete ref;
};
std::string program::str() const {
std::stringstream ss;
for (const auto& stmt : statements)
ss << stmt->str();
return ss.str();
};
} // namespace ast

View File

@@ -1,23 +0,0 @@
#pragma once
#include "ast.hpp"
#include <string>
#include <vector>
namespace ast {
struct program : public node {
std::vector<statement*> statements;
program() {}
program(std::vector<statement*> statements)
: statements(std::move(statements)) {};
std::string token_literal() const override;
virtual std::string str() const override;
~program();
};
} // namespace ast

View File

@@ -1,25 +0,0 @@
#include "block.hpp"
#include <sstream>
namespace ast {
block_stmt::block_stmt(token::token token): token(std::move(token)) {}
std::string block_stmt::token_literal() const {
return token.literal;
}
block_stmt::~block_stmt() {
for (const auto& stmt : statements)
delete stmt;
};
std::string block_stmt::str() const {
std::stringstream ss;
ss << "{";
for (const auto& stmt : statements)
ss << stmt->str();
ss << "}";
return ss.str();
};
} // namespace ast

View File

@@ -1,20 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
#include <vector>
namespace ast {
struct block_stmt : statement {
block_stmt(token::token token);
token::token token;
std::vector<ast::statement*> statements;
std::string token_literal() const override;
std::string str() const override;
~block_stmt();
};
} // namespace ast

View File

@@ -1,27 +0,0 @@
#include "expression.hpp"
#include <sstream>
namespace ast {
expression_stmt::expression_stmt(token::token token)
: token(std::move(token)),
expression(nullptr) {}
std::string expression_stmt::token_literal() const {
return token.literal;
}
expression_stmt::~expression_stmt() {
delete expression;
}
std::string expression_stmt::str() const {
std::stringstream ss;
if (expression != nullptr)
ss << expression->str();
return ss.str();
};
} // namespace ast

View File

@@ -1,18 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
namespace ast {
struct expression_stmt : statement {
expression_stmt(token::token token);
token::token token;
ast::expression* expression;
std::string token_literal() const override;
std::string str() const override;
~expression_stmt();
};
} // namespace ast

View File

@@ -1,36 +1,17 @@
#include "let.hpp" #include "let.hpp"
#include <sstream>
namespace ast { namespace ast {
let_stmt::let_stmt(token::token token) let::let(token::token token)
: token(std::move(token)), : token(std::move(token)),
name(nullptr), name(nullptr),
value(nullptr) {} value(nullptr) {}
let_stmt::let_stmt(token::token token, identifier* name, expression* value) std::string let::token_literal() const {
: token(std::move(token)),
name(name),
value(value) {}
std::string let_stmt::token_literal() const {
return token.literal; return token.literal;
} }
let_stmt::~let_stmt() { let::~let() {
delete name; delete name;
delete value; delete value;
}; };
std::string let_stmt::str() const {
std::stringstream ss;
ss << token_literal() << ' ' << name->str() << " = ";
if (value != nullptr)
ss << value->str();
ss << ';';
return ss.str();
};
} // namespace ast } // namespace ast

View File

@@ -5,17 +5,15 @@
#include "token/token.hpp" #include "token/token.hpp"
namespace ast { namespace ast {
struct let_stmt : statement { struct let : statement {
let_stmt(token::token token); let(token::token token);
let_stmt(token::token token, identifier* name, expression* value);
token::token token; token::token token;
identifier* name; identifier* name;
expression* value; expression* value;
std::string token_literal() const override; std::string token_literal() const override;
std::string str() const override;
~let_stmt(); ~let();
}; };
} // namespace ast } // namespace ast

View File

@@ -1,33 +0,0 @@
#include "return.hpp"
#include <sstream>
namespace ast {
return_stmt::return_stmt(token::token token)
: token(std::move(token)),
value(nullptr) {}
return_stmt::return_stmt(token::token token, expression* value)
: token(std::move(token)),
value(value) {}
std::string return_stmt::token_literal() const {
return token.literal;
}
return_stmt::~return_stmt() {
delete value;
};
std::string return_stmt::str() const {
std::stringstream ss;
ss << token_literal() << " ";
if (value != nullptr)
ss << value->str();
ss << ';';
return ss.str();
};
} // namespace ast

View File

@@ -1,19 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "token/token.hpp"
namespace ast {
struct return_stmt : statement {
return_stmt(token::token token);
return_stmt(token::token token, expression* value);
token::token token;
expression* value;
std::string token_literal() const override;
std::string str() const override;
~return_stmt();
};
} // namespace ast

View File

@@ -1,41 +0,0 @@
#include "evaluator.hpp"
#include "ast/ast.hpp"
#include "ast/expressions/boolean.hpp"
#include "ast/expressions/integer.hpp"
#include "ast/program.hpp"
#include "ast/statements/expression.hpp"
#include "object/boolean.hpp"
#include "object/integers.hpp"
#include "object/null.hpp"
#include <vector>
namespace eval {
static object::null* null = new object::null;
object::object* eval(std::vector<ast::statement*> statements) {
object::object* ret;
for (auto& stmt : statements)
ret = eval(stmt);
return ret;
}
object::object* eval(ast::node* node) {
if (ast::integer_literal* integer =
dynamic_cast<ast::integer_literal*>(node)) {
return new object::integer(integer->value);
} else if (ast::boolean_literal* boolean =
dynamic_cast<ast::boolean_literal*>(node)) {
return new object::boolean(boolean->value);
} else if (ast::program* program = dynamic_cast<ast::program*>(node)) {
return eval(program->statements);
} else if (ast::expression_stmt* expression_stmt =
dynamic_cast<ast::expression_stmt*>(node)) {
return eval(expression_stmt->expression);
}
return null;
}
} // namespace eval

View File

@@ -1,8 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "object/object.hpp"
namespace eval {
object::object* eval(ast::node*);
} // namespace eval

View File

@@ -7,8 +7,6 @@
#include <iostream> #include <iostream>
namespace lexer { namespace lexer {
lexer::lexer(std::istream& input): input(input) {}
token::token lexer::next_token() { token::token lexer::next_token() {
if (!(input >> c)) if (!(input >> c))
return {token::type::END_OF_FILE, ""}; return {token::type::END_OF_FILE, ""};

View File

@@ -1,12 +1,10 @@
#pragma once #pragma once
#include "token/token.hpp" #include "token/token.hpp"
#include <istream> #include <istream>
namespace lexer { namespace lexer {
struct lexer { struct lexer {
lexer(std::istream&);
std::istream& input; std::istream& input;
char c = 0; char c = 0;
token::token next_token(); token::token next_token();

View File

@@ -1,9 +0,0 @@
#include "boolean.hpp"
#include <string>
namespace object {
std::string boolean::inspect() {
return std::to_string(value);
}
} // namespace object

View File

@@ -1,13 +0,0 @@
#pragma once
#include "object.hpp"
namespace object {
struct boolean : object {
bool value;
::object::type type = type::BOOLEAN_OBJ;
explicit boolean(bool value): value(value) {}
std::string inspect();
};
} // namespace object

View File

@@ -1,9 +0,0 @@
#include "integers.hpp"
#include <string>
namespace object {
std::string integer::inspect() {
return std::to_string(value);
}
} // namespace object

View File

@@ -1,13 +0,0 @@
#pragma once
#include "object.hpp"
namespace object {
struct integer : object {
int value;
::object::type type = type::INTEGER_OBJ;
explicit integer(int value): value(value) {}
std::string inspect();
};
} // namespace object

View File

@@ -1,7 +0,0 @@
#include "null.hpp"
namespace object {
std::string null::inspect() {
return "null";
}
} // namespace object

View File

@@ -1,10 +0,0 @@
#pragma once
#include "object.hpp"
namespace object {
struct null : object {
::object::type type = type::NULL_OBJ;
std::string inspect();
};
} // namespace object

View File

@@ -1,13 +0,0 @@
#pragma once
#include "type.hpp"
#include <string>
namespace object {
struct object {
::object::type type;
virtual std::string inspect() = 0;
};
} // namespace object

View File

@@ -1,23 +0,0 @@
#include "type.hpp"
#include <array>
namespace object {
// Array mapping enum values to their string representations
constexpr std::
array<std::string_view, static_cast<size_t>(type::BOOLEAN_OBJ) + 1>
tokenTypeStrings = {
#define X(name, str) str,
OBJECT_LIST
#undef X
};
// Stream insertion operator using the lookup array
std::ostream& operator<<(std::ostream& os, type type) {
auto idx = static_cast<size_t>(type);
if (idx < tokenTypeStrings.size())
return os << tokenTypeStrings[idx];
return os << "Unknown";
}
} // namespace object

View File

@@ -1,21 +0,0 @@
#pragma once
#include <ostream>
namespace object {
// X-macro list of token types and their string representations
#define OBJECT_LIST \
X(NULL_OBJ, "NULL") \
X(INTEGER_OBJ, "INTEGER") \
X(BOOLEAN_OBJ, "BOOLEAN")
// Define the TokenType enum using the X-macro
enum class type {
#define X(name, str) name,
OBJECT_LIST
#undef X
};
std::ostream& operator<<(std::ostream&, type);
} // namespace object

View File

@@ -1,24 +1,10 @@
#include "parser.hpp" #include "parser.hpp"
#include "ast/errors/error.hpp" #include "ast/errors/error.hpp"
#include "ast/expressions/boolean.hpp"
#include "ast/expressions/function.hpp"
#include "ast/expressions/identifier.hpp"
#include "ast/expressions/if_then_else.hpp"
#include "ast/expressions/infix.hpp"
#include "ast/expressions/integer.hpp"
#include "ast/expressions/prefix.hpp"
#include "ast/statements/block.hpp"
#include "token/token.hpp"
#include "token/type.hpp" #include "token/type.hpp"
#include "utils/tracer.hpp"
#include <sstream> #include <sstream>
#define LOG_CUR_NEXT \
std::cout << "current: " << current << std::endl; \
std::cout << "next: " << next << std::endl;
namespace parser { namespace parser {
parser::parser(lexer::lexer& lexer) parser::parser(lexer::lexer& lexer)
: lexer(lexer), : lexer(lexer),
@@ -26,65 +12,6 @@ namespace parser {
next(token::type::ILLEGAL, "") { next(token::type::ILLEGAL, "") {
next_token(); next_token();
next_token(); next_token();
register_prefix(
token::type::IDENTIFIER,
std::bind(&parser::parse_identifier, this)
);
register_prefix(
token::type::INT,
std::bind(&parser::parse_integer, this)
);
register_prefix(
{
token::type::BANG,
token::type::MINUS,
},
std::bind(&parser::parse_prefix_expr, this)
);
register_prefix(
{
token::type::TRUE,
token::type::FALSE,
},
std::bind(&parser::parse_boolean, this)
);
register_prefix(
token::type::LPAREN,
std::bind(&parser::parse_grouped_expr, this)
);
register_prefix(
token::type::IF,
std::bind(&parser::parse_if_then_else, this)
);
register_prefix(
token::type::FUNCTION,
std::bind(&parser::parse_function_lit, this)
);
using namespace std::placeholders;
register_infix(
{token::type::PLUS,
token::type::MINUS,
token::type::ASTERISK,
token::type::SLASH,
token::type::EQ,
token::type::NEQ,
token::type::GT,
token::type::LT},
std::bind(&parser::parse_infix_expr, this, _1)
);
register_infix(
token::type::LPAREN,
std::bind(&parser::parse_function_call, this, _1)
);
} }
void parser::next_token() { void parser::next_token() {
@@ -92,14 +19,8 @@ namespace parser {
next = lexer.next_token(); next = lexer.next_token();
} }
void parser::skip_until_semicolon() { ast::program* parser::parse_program() {
for (; current.type != token::type::SEMICOLON ast::program* p = new ast::program();
&& current.type != token::type::END_OF_FILE;
next_token()) {};
}
std::unique_ptr<ast::program> parser::parse_program() {
std::unique_ptr<ast::program> p = std::make_unique<ast::program>();
for (; current.type != token::type::END_OF_FILE; next_token()) { for (; current.type != token::type::END_OF_FILE; next_token()) {
ast::statement* stmt = parse_statement(); ast::statement* stmt = parse_statement();
@@ -107,6 +28,7 @@ namespace parser {
p->statements.push_back(stmt); p->statements.push_back(stmt);
} }
return p; return p;
} }
@@ -114,83 +36,10 @@ namespace parser {
switch (current.type) { switch (current.type) {
case token::type::LET: case token::type::LET:
return parse_let(); return parse_let();
case token::type::RETURN:
return parse_return();
default: default:
return parse_expression_stmt();
}
}
ast::expression* parser::parse_expression(precedence prec) {
TRACE_FUNCTION;
auto prefix_it = prefix_parse_fns.find(current.type);
if (prefix_it == prefix_parse_fns.end()) {
unkown_prefix_error(current);
return nullptr; return nullptr;
} }
prefix_parse_fn prefix = prefix_it->second;
ast::expression* left = prefix();
while (next.type != token::type::SEMICOLON
&& prec < precedence_for(next.type)) {
auto infix_it = infix_parse_fns.find(next.type);
if (infix_it == infix_parse_fns.end())
return left;
next_token();
infix_parse_fn infix = infix_it->second;
left = infix(left);
} }
return left;
};
ast::return_stmt* parser::parse_return() {
ast::return_stmt* stmt = new ast::return_stmt(current);
next_token();
stmt->value = parse_expression();
if (next.type == token::type::SEMICOLON)
next_token();
return stmt;
}
ast::let_stmt* parser::parse_let() {
ast::let_stmt* stmt = new ast::let_stmt(current);
if (!expect_next(token::type::IDENTIFIER)) {
delete stmt;
skip_until_semicolon();
return nullptr;
}
stmt->name = new ast::identifier{current, current.literal};
if (!expect_next(token::type::ASSIGN)) {
delete stmt;
skip_until_semicolon();
return nullptr;
}
next_token();
stmt->value = parse_expression();
if (next.type == token::type::SEMICOLON)
next_token();
return stmt;
}
ast::expression_stmt* parser::parse_expression_stmt() {
TRACE_FUNCTION;
ast::expression_stmt* stmt = new ast::expression_stmt(current);
stmt->expression = parse_expression();
if (next.type == token::type::SEMICOLON)
next_token();
return stmt;
};
bool parser::expect_next(token::type t) { bool parser::expect_next(token::type t) {
if (next.type == t) { if (next.type == t) {
@@ -201,6 +50,27 @@ namespace parser {
return false; return false;
} }
ast::let* parser::parse_let() {
ast::let* stmt = new ast::let(current);
if (!expect_next(token::type::IDENTIFIER)) {
delete stmt;
return nullptr;
}
stmt->name = new ast::identifier{current, current.literal};
if (!expect_next(token::type::ASSIGN)) {
delete stmt;
return nullptr;
}
// TODO: we are currently skipping expressions until we encounter a
// semicolon
for (; current.type != token::type::SEMICOLON; next_token()) {}
return stmt;
}
void parser::next_error(token::type t) { void parser::next_error(token::type t) {
std::stringstream ss; std::stringstream ss;
ss << "Expected next token to be " << t << " but instead got " ss << "Expected next token to be " << t << " but instead got "
@@ -208,222 +78,8 @@ namespace parser {
errors.push_back(new ast::error::expected_next(t, ss.str())); errors.push_back(new ast::error::expected_next(t, ss.str()));
} }
void parser::unkown_prefix_error(token::token tok) {
std::stringstream ss;
ss << "No prefix parse function for token " << tok;
errors.push_back(new ast::error::unkown_prefix(tok, ss.str()));
}
parser::~parser() { parser::~parser() {
for (const auto& e : errors) for (const auto& e : errors)
delete e; delete e;
} }
void parser::register_prefix(token::type type, prefix_parse_fn fn) {
prefix_parse_fns[type] = fn;
};
void parser::register_prefix(
std::vector<token::type> types, prefix_parse_fn fn
) {
for (auto& type : types)
register_prefix(type, fn);
};
void parser::register_infix(token::type type, infix_parse_fn fn) {
infix_parse_fns[type] = fn;
};
void
parser::register_infix(std::vector<token::type> types, infix_parse_fn fn) {
for (auto& type : types)
register_infix(type, fn);
};
ast::identifier* parser::parse_identifier() {
return new ast::identifier(current, current.literal);
};
ast::integer_literal* parser::parse_integer() {
TRACE_FUNCTION;
return new ast::integer_literal(current, std::stoi(current.literal));
};
ast::boolean_literal* parser::parse_boolean() {
TRACE_FUNCTION;
return new ast::boolean_literal(
current,
current.type == token::type::TRUE
);
};
static void free_vec(std::vector<ast::identifier*> v) {
for (auto& e : v)
delete e;
}
std::vector<ast::identifier*> parser::parse_function_parameters() {
if (next.type == token::type::RPAREN) {
next_token();
return {}; // no params
}
std::vector<ast::identifier*> ret;
if (!expect_next(token::type::IDENTIFIER))
return {};
ret.push_back(parse_identifier());
while (next.type == token::type::COMMA) {
next_token();
if (!expect_next(token::type::IDENTIFIER)) {
free_vec(ret);
return {};
}
ret.push_back(parse_identifier());
}
if (current.type == token::type::COMMA
&& next.type == token::type::RPAREN) {
next_error(token::type::IDENTIFIER);
free_vec(ret);
return {};
}
if (!expect_next(token::type::RPAREN)) {
free_vec(ret);
return {};
}
return ret;
}
ast::function_literal* parser::parse_function_lit() {
TRACE_FUNCTION;
ast::function_literal* ret = new ast::function_literal(current);
if (!expect_next(token::type::LPAREN)) {
delete ret;
return nullptr;
}
ret->parameters = parse_function_parameters();
if (!expect_next(token::type::LBRACE)) {
delete ret;
return nullptr;
}
ret->body = parse_block();
return ret;
};
ast::prefix_expr* parser::parse_prefix_expr() {
TRACE_FUNCTION;
ast::prefix_expr* ret = new ast::prefix_expr(current, current.literal);
next_token();
ret->right = parse_expression(precedence::PREFIX);
return ret;
};
ast::expression* parser::parse_grouped_expr() {
TRACE_FUNCTION;
next_token();
ast::expression* ret = parse_expression(precedence::LOWEST);
if (!expect_next(token::type::RPAREN)) {
delete ret;
return nullptr;
}
return ret;
};
ast::if_then_else* parser::parse_if_then_else() {
TRACE_FUNCTION;
ast::if_then_else* ret = new ast::if_then_else(current);
if (!expect_next(token::type::LPAREN)) {
delete ret;
return nullptr;
}
next_token();
ret->condition = parse_expression();
if (!expect_next(token::type::RPAREN)) {
delete ret;
return nullptr;
}
if (!expect_next(token::type::LBRACE)) {
delete ret;
return nullptr;
}
ret->consequence = parse_block();
if (next.type != token::type::ELSE)
return ret;
next_token();
if (!expect_next(token::type::LBRACE)) {
delete ret;
return nullptr;
}
ret->alternative = parse_block();
return ret;
};
ast::block_stmt* parser::parse_block() {
TRACE_FUNCTION;
ast::block_stmt* ret = new ast::block_stmt(current);
for (next_token(); current.type != token::type::RBRACE
&& current.type != token::type::END_OF_FILE;
next_token()) {
ast::statement* stmt = parse_statement();
if (stmt != nullptr)
ret->statements.push_back(stmt);
}
if (current.type != token::type::RBRACE) {
next_error(token::type::RBRACE);
delete ret;
return nullptr;
}
return ret;
}
ast::infix_expr* parser::parse_infix_expr(ast::expression* left) {
TRACE_FUNCTION;
ast::infix_expr* ret =
new ast::infix_expr(current, current.literal, left);
precedence prec = precedence_for(current.type);
next_token();
ret->right = parse_expression(prec);
return ret;
};
ast::function_call* parser::parse_function_call(ast::expression* target) {
TRACE_FUNCTION;
ast::function_call* ret = new ast::function_call(current, target);
next_token();
if (current.type == token::type::RPAREN)
return ret;
ret->parameters.push_back(parse_expression());
// parameters
while (next.type == token::type::COMMA) {
next_token();
next_token();
ret->parameters.push_back(parse_expression());
}
if (!expect_next(token::type::RPAREN)) {
delete ret;
return nullptr;
}
return ret;
};
} // namespace parser } // namespace parser

View File

@@ -2,72 +2,26 @@
#include "ast/ast.hpp" #include "ast/ast.hpp"
#include "ast/errors/error.hpp" #include "ast/errors/error.hpp"
#include "ast/expressions/boolean.hpp"
#include "ast/expressions/function.hpp"
#include "ast/expressions/identifier.hpp"
#include "ast/expressions/if_then_else.hpp"
#include "ast/expressions/infix.hpp"
#include "ast/expressions/integer.hpp"
#include "ast/expressions/prefix.hpp"
#include "ast/program.hpp"
#include "ast/statements/block.hpp"
#include "ast/statements/expression.hpp"
#include "ast/statements/let.hpp" #include "ast/statements/let.hpp"
#include "ast/statements/return.hpp"
#include "lexer/lexer.hpp" #include "lexer/lexer.hpp"
#include "precedence.hpp"
#include "token/token.hpp" #include "token/token.hpp"
#include <functional>
#include <memory>
#include <vector>
namespace parser { namespace parser {
using prefix_parse_fn = std::function<ast::expression*()>;
using infix_parse_fn = std::function<ast::expression*(ast::expression*)>;
struct parser { struct parser {
parser(lexer::lexer&); parser(lexer::lexer& lexer);
~parser(); ~parser();
std::vector<ast::error::error*> errors; std::vector<ast::error::error*> errors;
std::unique_ptr<ast::program> parse_program(); ast::program* parse_program();
private: private:
lexer::lexer& lexer; lexer::lexer& lexer;
token::token current, next; token::token current, next;
std::unordered_map<token::type, prefix_parse_fn> prefix_parse_fns;
std::unordered_map<token::type, infix_parse_fn> infix_parse_fns;
void next_token(); void next_token();
void skip_until_semicolon();
ast::statement* parse_statement(); ast::statement* parse_statement();
ast::expression* parse_expression(precedence = precedence::LOWEST); ast::let* parse_let();
ast::let_stmt* parse_let();
ast::return_stmt* parse_return();
ast::expression_stmt* parse_expression_stmt();
bool expect_next(token::type); bool expect_next(token::type);
void next_error(token::type); void next_error(token::type);
void unkown_prefix_error(token::token);
void register_prefix(token::type, prefix_parse_fn);
void register_prefix(std::vector<token::type>, prefix_parse_fn);
void register_infix(token::type, infix_parse_fn);
void register_infix(std::vector<token::type>, infix_parse_fn);
ast::identifier* parse_identifier();
ast::integer_literal* parse_integer();
ast::boolean_literal* parse_boolean();
std::vector<ast::identifier*> parse_function_parameters();
ast::function_literal* parse_function_lit();
ast::prefix_expr* parse_prefix_expr();
ast::expression* parse_grouped_expr();
ast::if_then_else* parse_if_then_else();
ast::block_stmt* parse_block();
ast::infix_expr* parse_infix_expr(ast::expression*);
ast::function_call* parse_function_call(ast::expression*);
}; };
} // namespace parser } // namespace parser

View File

@@ -1,24 +0,0 @@
#include "precedence.hpp"
namespace parser {
precedence precedence_for(token::type type) {
switch (type) {
case token::type::EQ:
case token::type::NEQ:
return precedence::EQUALS;
case token::type::LT:
case token::type::GT:
return precedence::LESS_GREATER;
case token::type::PLUS:
case token::type::MINUS:
return precedence::SUM;
case token::type::ASTERISK:
case token::type::SLASH:
return precedence::PRODUCT;
case token::type::LPAREN:
return precedence::CALL;
default:
return precedence::LOWEST;
}
}
} // namespace parser

View File

@@ -1,21 +0,0 @@
#include "token/type.hpp"
#include <ostream>
namespace parser {
enum class precedence {
LOWEST,
EQUALS,
LESS_GREATER,
SUM,
PRODUCT,
PREFIX,
CALL
};
precedence precedence_for(token::type);
inline std::ostream& operator<<(std::ostream& os, precedence& p) {
return os << static_cast<int>(p);
}
} // namespace parser

View File

@@ -1,10 +1,7 @@
#include "repl.hpp" #include "repl.hpp"
#include "ast/program.hpp"
#include "lexer/lexer.hpp" #include "lexer/lexer.hpp"
#include "parser/parser.hpp"
#include <memory>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -22,14 +19,11 @@ namespace repl {
std::istringstream ss(line); std::istringstream ss(line);
lexer::lexer l{ss}; lexer::lexer l{ss};
parser::parser p{l}; for (token::token tok = l.next_token();
std::unique_ptr<ast::program> program = p.parse_program(); tok.type != token::type::END_OF_FILE;
if (!p.errors.empty()) { tok = l.next_token())
for (auto& e : p.errors) out << tok << " ";
out << e->what() << std::endl; out << std::endl;
continue;
}
out << program->str() << std::endl;
} }
} }

View File

@@ -1,30 +0,0 @@
#pragma once
#include <iostream>
#include <string>
namespace {
struct FunctionTracer {
std::string name;
inline static int tab_counter = 0;
FunctionTracer(const std::string& func): name(func) {
std::cout << std::string(tab_counter++, '\t') << "BEGIN " << name
<< std::endl;
}
~FunctionTracer() {
std::cout << std::string(--tab_counter, '\t') << "Exiting " << name
<< std::endl;
}
};
} // namespace
#ifndef TRACE
#define TRACE 0
#endif
#if TRACE
#define TRACE_FUNCTION FunctionTracer tracer(__FUNCTION__);
#else
#define TRACE_FUNCTION
#endif

View File

@@ -1,20 +0,0 @@
#include "doctest.h"
#include "utils.hpp"
TEST_SUITE("Eval: integers") {
TEST_CASE_FIXTURE(
test::utils::EvalFixture,
"Simple integer expression statements"
) {
struct test_struct {
std::string input;
int expected;
};
struct test_struct tests[]{{"5", 5}, {"10", 10}};
for (auto& t : tests) {
setup(t.input);
test::utils::test_integer_object(result.get(), t.expected);
}
}
}

View File

@@ -6,7 +6,6 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
TEST_SUITE("Lexer") {
TEST_CASE("Single character token") { TEST_CASE("Single character token") {
struct test { struct test {
token::type expectedType; token::type expectedType;
@@ -160,4 +159,3 @@ if (5 < 10) {\
REQUIRE(tok.literal == t.expectedLiteral); REQUIRE(tok.literal == t.expectedLiteral);
} }
}; };
}

134
test/parser.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include "parser/parser.hpp"
#include "ast/ast.hpp"
#include "ast/statements/let.hpp"
#include "lexer/lexer.hpp"
#include <doctest.h>
#include <iostream>
#include <sstream>
void test_let_statement(ast::statement* stmt, const std::string name) {
REQUIRE(stmt->token_literal() == "let");
ast::let* let_stmt;
REQUIRE_NOTHROW(let_stmt = dynamic_cast<ast::let*>(stmt));
REQUIRE_MESSAGE(
let_stmt != nullptr,
"Couldn't cast statement to a let statement"
);
REQUIRE(let_stmt->name->value == name);
REQUIRE(let_stmt->name->token_literal() == name);
}
void test_failing_let_parsing(
std::string input_s,
std::vector<token::type> expected_types,
int n_good_statements = 0
) {
std::stringstream input(input_s);
lexer::lexer l{input};
parser::parser p{l};
ast::program* program = p.parse_program();
// Check for errors
REQUIRE(p.errors.size() == expected_types.size());
int i = 0;
for (auto& e : p.errors) {
ast::error::expected_next* en;
REQUIRE_NOTHROW(en = dynamic_cast<ast::error::expected_next*>(e));
REQUIRE_MESSAGE(
en != nullptr,
"Couldn't cast the error to an 'expected_next'"
);
REQUIRE(en->expected_type == expected_types[i++]);
}
// normal program check
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
REQUIRE(program->statements.size() == n_good_statements);
delete program;
}
void check_parser_errors(const std::vector<ast::error::error*>& errors) {
if (errors.empty())
return;
std::cerr << "parser has " << errors.size() << " errors:\n";
for (const auto& error : errors)
std::cerr << '\t' << error->what() << "\n";
// Use doctest's FAIL macro to immediately stop
FAIL_CHECK("Parser had errors. See stderr for details.");
}
TEST_CASE("Malformed let statement (checking for memory leaks)") {
SUBCASE("Second token not identifier") {
test_failing_let_parsing("let 5 = 5;", {token::type::IDENTIFIER});
}
SUBCASE("Third token not '='") {
test_failing_let_parsing("let five ! 5;", {token::type::ASSIGN});
}
SUBCASE("Missing both identifier and '='") {
test_failing_let_parsing("let 5;", {token::type::IDENTIFIER});
}
SUBCASE("Multiple parsing errors") {
test_failing_let_parsing(
"let 5; let ! = 5; let five = 5; let five 5; let;",
{token::type::IDENTIFIER,
token::type::IDENTIFIER,
token::type::ASSIGN,
token::type::IDENTIFIER},
1
);
}
}
TEST_CASE("Parse let statement") {
std::stringstream input("\
let x = 5;\
let y = 10;\
let foobar = 103213;\
");
lexer::lexer l{input};
parser::parser p{l};
ast::program* program = p.parse_program();
check_parser_errors(p.errors);
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
REQUIRE(program->statements.size() == 3);
struct test {
std::string expected_identifier;
};
test tests[]{
"x",
"y",
"foobar",
};
int i = 0;
for (const auto& t : tests) {
ast::statement* stmt = program->statements[i++];
test_let_statement(stmt, t.expected_identifier);
}
delete program;
}

View File

@@ -1,278 +0,0 @@
#include "ast/expressions/identifier.hpp"
#include "ast/expressions/infix.hpp"
#include "ast/expressions/prefix.hpp"
#include "utils.hpp"
#include <doctest.h>
TEST_SUITE("Parser: expression") {
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Simple expression statement with identifier"
) {
setup("foobar;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::identifier* ident =
test::utils::cast<ast::identifier>(expression_stmt->expression);
REQUIRE(ident->value == "foobar");
REQUIRE(ident->token_literal() == "foobar");
};
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Simple expression statement with integer"
) {
setup("5;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
test::utils::test_integer_literal(expression_stmt->expression, 5);
};
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Simple statements with booleans"
) {
SUBCASE("True literal") {
setup("true;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
test::utils::test_boolean_literal(
expression_stmt->expression,
true
);
}
SUBCASE("False literal") {
setup("false;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
test::utils::test_boolean_literal(
expression_stmt->expression,
false
);
}
SUBCASE("True let statement") {
setup("let foo = true;");
REQUIRE(program->statements.size() == 1);
ast::let_stmt* let_stmt =
test::utils::cast<ast::let_stmt>(program->statements[0]);
CHECK(let_stmt->name->value == "foo");
test::utils::test_boolean_literal(let_stmt->value, true);
}
SUBCASE("False let statement") {
setup("let bar = false;");
REQUIRE(program->statements.size() == 1);
ast::let_stmt* let_stmt =
test::utils::cast<ast::let_stmt>(program->statements[0]);
CHECK(let_stmt->name->value == "bar");
test::utils::test_boolean_literal(let_stmt->value, false);
}
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Simple expression statement with prefix before integer"
) {
#define CASE(name, input, _op, _right) \
SUBCASE(name) { \
setup(input); \
\
REQUIRE(program->statements.size() == 1); \
ast::expression_stmt* expression_stmt = \
test::utils::cast<ast::expression_stmt>(program->statements[0]); \
\
ast::prefix_expr* prefix_expr = \
test::utils::cast<ast::prefix_expr>(expression_stmt->expression); \
\
REQUIRE(prefix_expr->op == _op); \
test::utils::test_integer_literal(prefix_expr->right, _right); \
}
CASE("Prefix: '!'", "!5;", "!", 5);
CASE("Prefix: '-'", "-15;", "-", 15);
#undef CASE
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Simple expression statement with infix between integers"
) {
#define CASE(name, input, _left, _op, _right) \
SUBCASE(name) { \
setup(input); \
REQUIRE(program->statements.size() == 1); \
\
ast::expression_stmt* expression_stmt = \
test::utils::cast<ast::expression_stmt>(program->statements[0]); \
\
test::utils::test_infix_expression( \
expression_stmt->expression, \
_left, \
_op, \
_right \
); \
}
CASE("Infix: '+'", "5 + 5;", 5, "+", 5);
CASE("Infix: '-'", "5- 5;", 5, "-", 5);
CASE("Infix: '*'", "15 *5;", 15, "*", 5);
CASE("Infix: '/'", "15 / 5;", 15, "/", 5);
CASE("Infix: '<'", "15 < 15;", 15, "<", 15);
CASE("Infix: '>'", "25 > 15;", 25, ">", 15);
CASE("Infix: '=='", "5 == 5;", 5, "==", 5);
CASE("Infix: '!='", "15 != 5;", 15, "!=", 5);
CASE("Infix: between identifiers", "alice * bob;", "alice", "*", "bob");
CASE("Infix: between booleans", "true == bob;", true, "==", "bob");
CASE("Infix: between booleans", "true != false;", true, "!=", false);
#undef CASE
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Slightly more complex infix expression"
) {
setup("5 - -15;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::infix_expr* infix_expr =
test::utils::cast<ast::infix_expr>(expression_stmt->expression);
test::utils::test_integer_literal(infix_expr->left, 5);
CHECK(infix_expr->op == "-");
ast::prefix_expr* prefix_expr =
test::utils::cast<ast::prefix_expr>(infix_expr->right);
CHECK(prefix_expr->op == "-");
test::utils::test_integer_literal(prefix_expr->right, 15);
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Slightly even more complex infix expression"
) {
setup("5 - -15 * 3;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::infix_expr* infix_expr =
test::utils::cast<ast::infix_expr>(expression_stmt->expression);
test::utils::test_integer_literal(infix_expr->left, 5);
CHECK(infix_expr->op == "-");
ast::infix_expr* second_infix_expr =
test::utils::cast<ast::infix_expr>(infix_expr->right);
CHECK(second_infix_expr->op == "*");
ast::prefix_expr* prefix_expr =
test::utils::cast<ast::prefix_expr>(second_infix_expr->left);
CHECK(prefix_expr->op == "-");
test::utils::test_integer_literal(prefix_expr->right, 15);
test::utils::test_integer_literal(second_infix_expr->right, 3);
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Checking operator precedence with simple example"
) {
setup("5 * -15 - 3;");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expression_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::infix_expr* infix_expr =
test::utils::cast<ast::infix_expr>(expression_stmt->expression);
ast::infix_expr* second_infix_expr =
test::utils::cast<ast::infix_expr>(infix_expr->left);
CHECK(second_infix_expr->op == "*");
test::utils::test_integer_literal(second_infix_expr->left, 5);
ast::prefix_expr* prefix_expr =
test::utils::cast<ast::prefix_expr>(second_infix_expr->right);
CHECK(prefix_expr->op == "-");
test::utils::test_integer_literal(prefix_expr->right, 15);
CHECK(infix_expr->op == "-");
test::utils::test_integer_literal(infix_expr->right, 3);
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Full Operator Precedence test"
) {
struct test {
std::string input;
std::string expected;
};
struct test tests[]{
{"-a * b", "((-a) * b)"},
{"!-a", "(!(-a))"},
{"a + b + c", "((a + b) + c)"},
{"a + b - c", "((a + b) - c)"},
{"a * b * c", "((a * b) * c)"},
{"a * b / c", "((a * b) / c)"},
{"a + b / c", "(a + (b / c))"},
{"a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"},
{"3 + 4; -5 * 5", "(3 + 4)((-5) * 5)"},
{"5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"},
{"5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"},
{"3 + 4 * 5 == 3 * 1 + 4 * 5",
"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))"},
{"3 > 5 == false", "((3 > 5) == false)"},
{"3 < 5 == true", "((3 < 5) == true)"},
{"1 + (2 + 3)", "(1 + (2 + 3))"},
{"(1 + a) * 17", "((1 + a) * 17)"},
{"2 / (5 + 5)", "(2 / (5 + 5))"},
{"-(5 + a)", "(-(5 + a))"},
{"!(true == true)", "(!(true == true))"},
{"a + add(b * c) + d", "((a + add((b * c))) + d)"},
{"add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))",
"add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))"},
{"add(a + b + c * d / f + g)",
"add((((a + b) + ((c * d) / f)) + g))"},
};
for (auto& t : tests) {
setup(t.input);
CHECK(program->str() == t.expected);
}
}
TEST_CASE_FIXTURE(test::utils::ParserFixture, "Test to see trace") {
setup("-1 * 2 - 3");
CHECK(program->str() == "(((-1) * 2) - 3)");
setup("-1 - 2 * 3");
CHECK(program->str() == "((-1) - (2 * 3))");
}
}

View File

@@ -1,372 +0,0 @@
#include "ast/expressions/function.hpp"
#include "ast/errors/error.hpp"
#include "ast/statements/return.hpp"
#include "token/type.hpp"
#include "utils.hpp"
#include <doctest.h>
TEST_SUITE("Parser: function literal") {
TEST_CASE("Malformed function literal (checking for memory leaks)") {
SUBCASE("Missing opening paren no param") {
test::utils::test_failing_parsing(
"fn ) { return 2; }",
{token::type::LPAREN}
);
}
SUBCASE("Missing opening paren one param") {
test::utils::test_failing_parsing(
"fn x) { return 2; }",
{token::type::LPAREN}
);
}
SUBCASE("Missing opening paren two param") {
test::utils::test_failing_parsing(
"fn x, y) { return 2; }",
{token::type::LPAREN}
);
}
SUBCASE("Missing identifier with no closing paren - no param") {
test::utils::test_failing_parsing(
"fn ( { return 2; }",
{token::type::IDENTIFIER}
);
}
SUBCASE("Missing identifier with no closing paren - one param") {
test::utils::test_failing_parsing(
"fn (x, { return 2; }",
{token::type::IDENTIFIER}
);
}
SUBCASE("Missing identifier with no closing paren - two params") {
test::utils::test_failing_parsing(
"fn (x, y, { return 2; }",
{token::type::IDENTIFIER}
);
}
SUBCASE("Missing identifier with closing paren - one param") {
test::utils::test_failing_parsing(
"fn (x,) { return 2; }",
{token::type::IDENTIFIER}
);
}
SUBCASE("Missing identifier with closing paren - two params") {
test::utils::test_failing_parsing(
"fn (x,y,) { return 2; }",
{token::type::IDENTIFIER}
);
}
SUBCASE("Missing closing paren one param") {
test::utils::test_failing_parsing(
"fn (x { return 2; }",
{token::type::RPAREN}
);
}
SUBCASE("Missing closing paren two params") {
test::utils::test_failing_parsing(
"fn (x, y { return 2; }",
{token::type::RPAREN}
);
}
SUBCASE("Missing opening brace") {
test::utils::test_failing_parsing(
"fn (x, y) return x + y; }",
{token::type::LBRACE}
);
}
SUBCASE("Missing closing brace") {
test::utils::test_failing_parsing(
"fn (x, y) { return x + y; ",
{token::type::RBRACE}
);
}
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Parse well formed function literal"
) {
SUBCASE("no param function") {
setup("\
fn () {\
return true;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "fn");
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_literal* fun =
test::utils::cast<ast::function_literal>(expr_stmt->expression);
// parameters
CHECK(fun->parameters.size() == 0);
// block
REQUIRE(fun->body->statements.size() == 1);
ast::return_stmt* ret =
test::utils::cast<ast::return_stmt>(fun->body->statements[0]);
test::utils::test_boolean_literal(ret->value, true);
// full string
CHECK(fun->str() == "fn (){return true;}");
}
SUBCASE("one param function") {
setup("\
fn (x) {\
return x + 1;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "fn");
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_literal* fun =
test::utils::cast<ast::function_literal>(expr_stmt->expression);
// parameters
REQUIRE(fun->parameters.size() == 1);
test::utils::test_identifier(fun->parameters[0], "x");
// block
REQUIRE(fun->body->statements.size() == 1);
ast::return_stmt* ret =
test::utils::cast<ast::return_stmt>(fun->body->statements[0]);
test::utils::test_infix_expression(ret->value, "x", "+", 1);
// full string
CHECK(fun->str() == "fn (x){return (x + 1);}");
}
SUBCASE("two param function") {
setup("\
fn (x, y) {\
return x + y;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "fn");
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_literal* fun =
test::utils::cast<ast::function_literal>(expr_stmt->expression);
// parameters
REQUIRE(fun->parameters.size() == 2);
test::utils::test_identifier(fun->parameters[0], "x");
test::utils::test_identifier(fun->parameters[1], "y");
// block
REQUIRE(fun->body->statements.size() == 1);
ast::return_stmt* ret =
test::utils::cast<ast::return_stmt>(fun->body->statements[0]);
test::utils::test_infix_expression(ret->value, "x", "+", "y");
// full string
CHECK(fun->str() == "fn (x, y){return (x + y);}");
}
SUBCASE("two param function assigned to variable") {
setup("\
let fun = fn (x, y) {\
return x + y;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "let");
ast::let_stmt* let_stmt =
test::utils::cast<ast::let_stmt>(program->statements[0]);
// let lhs
test::utils::test_identifier(let_stmt->name, "fun");
// let rhs
CHECK(let_stmt->value->token_literal() == "fn");
ast::function_literal* fun =
test::utils::cast<ast::function_literal>(let_stmt->value);
// parameters
REQUIRE(fun->parameters.size() == 2);
test::utils::test_identifier(fun->parameters[0], "x");
test::utils::test_identifier(fun->parameters[1], "y");
// block
REQUIRE(fun->body->statements.size() == 1);
ast::return_stmt* ret =
test::utils::cast<ast::return_stmt>(fun->body->statements[0]);
test::utils::test_infix_expression(ret->value, "x", "+", "y");
// full string
CHECK(fun->str() == "fn (x, y){return (x + y);}");
}
}
}
TEST_SUITE("Parser: function call") {
TEST_CASE("Malformed function call (checking for memory leaks)") {
SUBCASE("missing closing no param function") {
std::stringstream input("value(;");
lexer::lexer l{input};
parser::parser p{l};
std::unique_ptr<ast::program> program = p.parse_program();
INFO(*program);
// Check for errors
REQUIRE(p.errors.size() == 2);
ast::error::unkown_prefix* up =
test::utils::cast<ast::error::unkown_prefix>(p.errors[0]);
REQUIRE(up->prefix.type == token::type::SEMICOLON);
ast::error::expected_next* en =
test::utils::cast<ast::error::expected_next>(p.errors[1]);
REQUIRE(en->expected_type == token::type::RPAREN);
// normal program check
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
}
SUBCASE("missing closing 1 param function") {
test::utils::test_failing_parsing(
"value(x;",
{token::type::RPAREN}
);
}
SUBCASE("missing closing 2 param function") {
test::utils::test_failing_parsing(
"value(x, y;",
{token::type::RPAREN}
);
}
SUBCASE("missing comma between 2 param function") {
test::utils::test_failing_parsing(
"value(x y);",
{token::type::RPAREN}
);
}
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Parse well formed function call"
) {
SUBCASE("no param function") {
setup("value();");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_call* fun =
test::utils::cast<ast::function_call>(expr_stmt->expression);
// target
test::utils::test_identifier(fun->target, "value");
// parameters
CHECK(fun->parameters.size() == 0);
// full string
CHECK(fun->str() == "value()");
}
SUBCASE("one param identifier function") {
setup("is_odd(1);");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_call* fun =
test::utils::cast<ast::function_call>(expr_stmt->expression);
// target
test::utils::test_identifier(fun->target, "is_odd");
// parameters
CHECK(fun->parameters.size() == 1);
test::utils::test_integer_literal(fun->parameters[0], 1);
// full string
CHECK(fun->str() == "is_odd(1)");
}
SUBCASE("two param function") {
setup("is_gt(1, a + 10);");
REQUIRE(program->statements.size() == 1);
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::function_call* fun =
test::utils::cast<ast::function_call>(expr_stmt->expression);
// target
test::utils::test_identifier(fun->target, "is_gt");
// parameters
CHECK(fun->parameters.size() == 2);
test::utils::test_integer_literal(fun->parameters[0], 1);
test::utils::test_infix_expression(
fun->parameters[1],
"a",
"+",
10
);
// full string
CHECK(fun->str() == "is_gt(1, (a + 10))");
}
SUBCASE("two param identifier function assigned to variable") {
setup("let res = add(1, a + 10, 2 * 3);");
REQUIRE(program->statements.size() == 1);
ast::let_stmt* let_stmt =
test::utils::cast<ast::let_stmt>(program->statements[0]);
test::utils::test_identifier(let_stmt->name, "res");
ast::function_call* fun =
test::utils::cast<ast::function_call>(let_stmt->value);
// target
test::utils::test_identifier(fun->target, "add");
// parameters
CHECK(fun->parameters.size() == 3);
test::utils::test_integer_literal(fun->parameters[0], 1);
test::utils::test_infix_expression(
fun->parameters[1],
"a",
"+",
10
);
test::utils::test_infix_expression(fun->parameters[2], 2, "*", 3);
// full string
CHECK(fun->str() == "add(1, (a + 10), (2 * 3))");
CHECK(let_stmt->str() == "let res = add(1, (a + 10), (2 * 3));");
}
}
}

View File

@@ -1,142 +0,0 @@
#include "ast/expressions/if_then_else.hpp"
#include "ast/expressions/infix.hpp"
#include "ast/statements/block.hpp"
#include "ast/statements/expression.hpp"
#include "utils.hpp"
#include <doctest.h>
TEST_SUITE("Parser: if") {
TEST_CASE("Malformed if then else (checking for memory leaks)") {
SUBCASE("Missing opening paren") {
test::utils::test_failing_parsing(
"if x > 15) {\
return x;\
}",
{token::type::LPAREN}
);
}
SUBCASE("Missing closing paren") {
test::utils::test_failing_parsing(
"if (x > 15 {\
return x;\
}",
{token::type::RPAREN}
);
}
SUBCASE("Missing opening brace") {
test::utils::test_failing_parsing(
"if (x > 15) \
return x;\
}",
{token::type::LBRACE}
);
}
SUBCASE("Missing closing brace") {
test::utils::test_failing_parsing(
"if (x > 15) { \
return x;\
",
{token::type::RBRACE}
);
}
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Parse well formed simple if "
) {
setup("\
if (x > 15) {\
return x;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "if");
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::if_then_else* if_stmt =
test::utils::cast<ast::if_then_else>(expr_stmt->expression);
// condition
ast::infix_expr* cond =
test::utils::cast<ast::infix_expr>(if_stmt->condition);
test::utils::test_infix_expression(cond, "x", ">", 15);
CHECK(if_stmt->condition->str() == "(x > 15)");
// consequence
REQUIRE(if_stmt->consequence->statements.size() == 1);
ast::return_stmt* ret_stmt = test::utils::cast<ast::return_stmt>(
if_stmt->consequence->statements[0]
);
test::utils::test_identifier(ret_stmt->value, "x");
CHECK(if_stmt->consequence->str() == "{return x;}");
// alternative
CHECK(if_stmt->alternative == nullptr);
// full string
CHECK(if_stmt->str() == "if (x > 15){return x;}");
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Parse well formed if then else"
) {
setup("\
if (x < 5) {\
return 15;\
} else {\
let b = 1;\
return x;\
}\
");
REQUIRE(program->statements.size() == 1);
CHECK(program->statements[0]->token_literal() == "if");
ast::expression_stmt* expr_stmt =
test::utils::cast<ast::expression_stmt>(program->statements[0]);
ast::if_then_else* if_stmt =
test::utils::cast<ast::if_then_else>(expr_stmt->expression);
// condition
ast::infix_expr* cond =
test::utils::cast<ast::infix_expr>(if_stmt->condition);
test::utils::test_infix_expression(cond, "x", "<", 5);
CHECK(if_stmt->condition->str() == "(x < 5)");
// consequence
REQUIRE(if_stmt->consequence->statements.size() == 1);
ast::return_stmt* ret_stmt = test::utils::cast<ast::return_stmt>(
if_stmt->consequence->statements[0]
);
test::utils::test_integer_literal(ret_stmt->value, 15);
CHECK(if_stmt->consequence->str() == "{return 15;}");
// alternative
REQUIRE(if_stmt->alternative != nullptr);
REQUIRE(if_stmt->alternative->statements.size() == 2);
ast::let_stmt* let_stmt = test::utils::cast<ast::let_stmt>(
if_stmt->alternative->statements[0]
);
test::utils::test_identifier(let_stmt->name, "b");
test::utils::test_integer_literal(let_stmt->value, 1);
ast::return_stmt* ret_stmt2 = test::utils::cast<ast::return_stmt>(
if_stmt->alternative->statements[1]
);
test::utils::test_identifier(ret_stmt2->value, "x");
CHECK(if_stmt->alternative->str() == "{let b = 1;return x;}");
// full string
CHECK(
if_stmt->str() == "if (x < 5){return 15;}else{let b = 1;return x;}"
);
}
}

View File

@@ -1,81 +0,0 @@
#include "ast/statements/let.hpp"
#include "ast/ast.hpp"
#include "utils.hpp"
#include <doctest.h>
#include <memory>
void test_let_statement(ast::statement* stmt, const std::string name) {
REQUIRE(stmt->token_literal() == "let");
ast::let_stmt* let_stmt = test::utils::cast<ast::let_stmt>(stmt);
REQUIRE(let_stmt->name->value == name);
REQUIRE(let_stmt->name->token_literal() == name);
}
TEST_SUITE("Parser: let") {
TEST_CASE("Malformed let statement (checking for memory leaks)") {
SUBCASE("Second token not identifier") {
test::utils::test_failing_parsing(
"let 5 = 5;",
{token::type::IDENTIFIER}
);
}
SUBCASE("Third token not '='") {
test::utils::test_failing_parsing(
"let five ! 5;",
{token::type::ASSIGN}
);
}
SUBCASE("Missing both identifier and '='") {
test::utils::test_failing_parsing(
"let 5;",
{token::type::IDENTIFIER}
);
}
SUBCASE("Multiple parsing errors") {
test::utils::test_failing_parsing(
"let 5; let ! = 5; let five = 5; let five 5; let;",
{token::type::IDENTIFIER,
token::type::IDENTIFIER,
token::type::ASSIGN,
token::type::IDENTIFIER},
1
);
}
}
TEST_CASE_FIXTURE(
test::utils::ParserFixture,
"Parse well formed let statements"
) {
setup("\
let x = 5;\
let y = 10;\
let foobar = 103213;\
");
REQUIRE(program->statements.size() == 3);
struct test {
std::string expected_identifier;
};
test tests[]{
"x",
"y",
"foobar",
};
int i = 0;
for (const auto& t : tests) {
ast::statement* stmt = program->statements[i++];
test_let_statement(stmt, t.expected_identifier);
}
}
}

View File

@@ -1,51 +0,0 @@
#include "parser/parser.hpp"
#include <doctest.h>
TEST_SUITE("Precedence") {
TEST_CASE("Raw precedence") {
CHECK(parser::precedence::LOWEST < parser::precedence::EQUALS);
CHECK(parser::precedence::LOWEST < parser::precedence::LESS_GREATER);
CHECK(parser::precedence::LOWEST < parser::precedence::SUM);
CHECK(parser::precedence::LOWEST < parser::precedence::PRODUCT);
CHECK(parser::precedence::LOWEST < parser::precedence::PREFIX);
CHECK(parser::precedence::LOWEST < parser::precedence::CALL);
CHECK(parser::precedence::EQUALS < parser::precedence::LESS_GREATER);
CHECK(parser::precedence::EQUALS < parser::precedence::SUM);
CHECK(parser::precedence::EQUALS < parser::precedence::PRODUCT);
CHECK(parser::precedence::EQUALS < parser::precedence::PREFIX);
CHECK(parser::precedence::EQUALS < parser::precedence::CALL);
CHECK(parser::precedence::LESS_GREATER < parser::precedence::SUM);
CHECK(parser::precedence::LESS_GREATER < parser::precedence::PRODUCT);
CHECK(parser::precedence::LESS_GREATER < parser::precedence::PREFIX);
CHECK(parser::precedence::LESS_GREATER < parser::precedence::CALL);
CHECK(parser::precedence::SUM < parser::precedence::PRODUCT);
CHECK(parser::precedence::SUM < parser::precedence::PREFIX);
CHECK(parser::precedence::SUM < parser::precedence::CALL);
CHECK(parser::precedence::PRODUCT < parser::precedence::PREFIX);
CHECK(parser::precedence::PRODUCT < parser::precedence::CALL);
CHECK(parser::precedence::PREFIX < parser::precedence::CALL);
}
TEST_CASE("Operator precedence") {
auto& prec = parser::precedence_for;
CHECK(prec(token::type::EQ) == prec(token::type::NEQ));
CHECK(prec(token::type::LT) == prec(token::type::GT));
CHECK(prec(token::type::PLUS) == prec(token::type::MINUS));
CHECK(prec(token::type::ASTERISK) == prec(token::type::SLASH));
CHECK(prec(token::type::EQ) < prec(token::type::LT));
CHECK(prec(token::type::EQ) < prec(token::type::PLUS));
CHECK(prec(token::type::EQ) < prec(token::type::ASTERISK));
CHECK(prec(token::type::LT) < prec(token::type::PLUS));
CHECK(prec(token::type::LT) < prec(token::type::ASTERISK));
CHECK(prec(token::type::PLUS) < prec(token::type::ASTERISK));
}
}

View File

@@ -1,21 +0,0 @@
#include "utils.hpp"
#include <doctest.h>
TEST_SUITE("Parser: return") {
TEST_CASE_FIXTURE(test::utils::ParserFixture, "Parse return statement") {
setup("\
return 5;\
return 10;\
return 103213;\
return 12 + 34;\
");
REQUIRE(program->statements.size() == 4);
for (const auto stmt : program->statements) {
CHECK(stmt->token_literal() == "return");
test::utils::cast<ast::return_stmt>(stmt);
}
}
}

View File

@@ -1,37 +0,0 @@
#include "ast/program.hpp"
#include "ast/statements/let.hpp"
#include "ast/statements/return.hpp"
#include "token/type.hpp"
#include <doctest.h>
TEST_SUITE("Program") {
TEST_CASE("Let string") {
ast::program program({new ast::let_stmt(
token::token(token::type::LET, "let"),
new ast::identifier(
token::token(token::type::IDENTIFIER, "myVar"),
"myVar"
),
new ast::identifier(
token::token(token::type::IDENTIFIER, "anotherVar"),
"anotherVar"
)
)});
CHECK(program.str() == "let myVar = anotherVar;");
}
TEST_CASE("Return string") {
ast::program program({new ast::return_stmt(
token::token(token::type::RETURN, "return"),
new ast::identifier(
token::token(token::type::IDENTIFIER, "myVar"),
"myVar"
)
)});
CHECK(program.str() == "return myVar;");
}
}

View File

@@ -1,40 +0,0 @@
#include "evaluator/evaluator.hpp"
#include "object/object.hpp"
#include "utils.hpp"
#include <doctest.h>
namespace test::utils {
void check_parser_errors(const std::vector<ast::error::error*>& errors) {
if (errors.empty())
return;
INFO("parser has " << errors.size() << " errors:");
std::ostringstream combined;
for (auto& err : errors)
combined << " > " << err->what() << '\n';
INFO(combined.str());
FAIL("Parser had errors.");
}
void ParserFixture::setup(std::string source) {
input.clear();
input << source;
lexer = std::make_unique<lexer::lexer>(input);
parser = std::make_unique<parser::parser>(*lexer);
program = parser->parse_program();
check_parser_errors(parser->errors);
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
}
void EvalFixture::setup(std::string source) {
ParserFixture::setup(source);
result = std::unique_ptr<object::object>(eval::eval(program.get()));
}
} // namespace test::utils

View File

@@ -1,10 +0,0 @@
#include "object/integers.hpp"
#include "object/object.hpp"
#include "utils.hpp"
namespace test::utils {
void test_integer_object(object::object* obj, int expected) {
object::integer* i = cast<object::integer>(obj);
CHECK(i->value == expected);
}
} // namespace test::utils

View File

@@ -1,94 +0,0 @@
#include "utils.hpp"
namespace test::utils {
void test_identifier(ast::expression* expr, std::string value) {
ast::identifier* ident = cast<ast::identifier>(expr);
REQUIRE(ident->value == value);
REQUIRE(ident->token_literal() == value);
}
void test_integer_literal(ast::expression* expr, int value) {
ast::integer_literal* int_lit = cast<ast::integer_literal>(expr);
REQUIRE(int_lit->value == value);
std::ostringstream oss;
oss << value;
REQUIRE(int_lit->token_literal() == oss.str());
}
void test_boolean_literal(ast::expression* expr, bool value) {
ast::boolean_literal* bool_lit = cast<ast::boolean_literal>(expr);
REQUIRE(bool_lit->value == value);
std::ostringstream oss;
oss << (value ? "true" : "false");
REQUIRE(bool_lit->token_literal() == oss.str());
}
void test_literal_expression(ast::expression* exp, std::any& expected) {
if (expected.type() == typeid(int))
return test_integer_literal(exp, std::any_cast<int>(expected));
if (expected.type() == typeid(bool))
return test_boolean_literal(exp, std::any_cast<bool>(expected));
if (expected.type() == typeid(std::string))
return test_identifier(exp, std::any_cast<std::string>(expected));
if (expected.type() == typeid(const char*))
return test_identifier(exp, std::any_cast<const char*>(expected));
FAIL(
"Type of exp not handled. Got: " * demangle(typeid(*exp).name())
* " as expression and " * demangle(expected.type().name())
* " as expected"
);
}
void test_infix_expression(
ast::expression* exp, std::any left, std::string op, std::any right
) {
ast::infix_expr* op_exp = cast<ast::infix_expr>(exp);
test_literal_expression(op_exp->left, left);
CHECK(op_exp->op == op);
test_literal_expression(op_exp->right, right);
}
void test_failing_parsing(
std::string input_s,
std::vector<token::type> expected_types,
int n_good_statements
) {
std::stringstream input(input_s);
lexer::lexer l{input};
parser::parser p{l};
std::unique_ptr<ast::program> program = p.parse_program();
INFO(*program);
// Check for errors
REQUIRE(p.errors.size() >= expected_types.size());
// ^^ because even though you were thinking
// about a specific token to be there, other `expect_next -> false`
// might be triggered for subexpressions due to the first one
int i = 0;
for (auto& t : expected_types) {
ast::error::expected_next* en =
cast<ast::error::expected_next>(p.errors[i++]);
REQUIRE(en->expected_type == t);
}
// normal program check
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
REQUIRE(program->statements.size() >= n_good_statements);
// ^^ because even though you were
// thinking about a specific number of statements to be there, it
// failing for `expect_next` might trigger a sub-expression to be
// triggered correctly and be parsed as the expression_stmt
}
} // namespace test::utils

View File

@@ -1,91 +0,0 @@
#pragma once
#include "ast/ast.hpp"
#include "lexer/lexer.hpp"
#include "object/object.hpp"
#include "parser/parser.hpp"
#include <any>
#include <cxxabi.h>
#include <doctest.h>
#include <memory>
#include <sstream>
namespace test::utils {
void check_parser_errors(const std::vector<ast::error::error*>&);
struct ParserFixture {
std::stringstream input;
std::unique_ptr<lexer::lexer> lexer;
std::unique_ptr<parser::parser> parser;
std::unique_ptr<ast::program> program;
ParserFixture() = default;
virtual void setup(std::string);
};
struct EvalFixture : ParserFixture {
std::unique_ptr<object::object> result;
void setup(std::string);
};
// parser tests
void test_identifier(ast::expression*, std::string);
void test_integer_literal(ast::expression*, int);
void test_boolean_literal(ast::expression*, bool);
void test_literal_expression(ast::expression*, std::any&);
void
test_infix_expression(ast::expression*, std::any, std::string, std::any);
void test_failing_parsing(std::string, std::vector<token::type>, int = 0);
// eval tests
void test_integer_object(object::object*, int);
namespace {
std::string demangle(const char* name) {
int status = 0;
std::unique_ptr<char, decltype(&std::free)> demangled(
abi::__cxa_demangle(name, nullptr, nullptr, &status),
&std::free
);
return (status == 0 && demangled) ? demangled.get() : name;
}
template <typename T, typename Base>
T* cast_impl(Base* base) {
static_assert(
std::is_base_of_v<Base, T>,
"T must be derived from Base"
);
T* t;
REQUIRE_NOTHROW(t = dynamic_cast<T*>(base));
REQUIRE_MESSAGE(
t != nullptr,
"Couldn't cast " * demangle(typeid(*base).name())
* " (given as " * demangle(typeid(Base).name()) * ")"
* " to a " * demangle(typeid(T).name())
);
return t;
}
} // namespace
// Overloads for your known base types
template <typename T>
T* cast(ast::node* stmt) {
return cast_impl<T, ast::node>(stmt);
}
template <typename T>
T* cast(ast::error::error* err) {
return cast_impl<T, ast::error::error>(err);
}
template <typename T>
T* cast(object::object* err) {
return cast_impl<T, object::object>(err);
}
} // namespace test::utils