Compare commits
1 Commits
main
...
bed99e0d63
Author | SHA1 | Date | |
---|---|---|---|
|
bed99e0d63 |
5
Makefile
5
Makefile
@@ -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 $@
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# Auto‐include dependencies
|
# Auto‐include dependencies
|
||||||
|
@@ -1,3 +1,2 @@
|
|||||||
-I./include
|
-I./include
|
||||||
-I./src
|
-I./src
|
||||||
-I./test/utils
|
|
||||||
|
9
src/ast/ast.cpp
Normal file
9
src/ast/ast.cpp
Normal 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
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ast/ast.hpp"
|
|
||||||
#include "object/object.hpp"
|
|
||||||
|
|
||||||
namespace eval {
|
|
||||||
object::object* eval(ast::node*);
|
|
||||||
} // namespace eval
|
|
@@ -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, ""};
|
||||||
|
@@ -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();
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
#include "boolean.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace object {
|
|
||||||
std::string boolean::inspect() {
|
|
||||||
return std::to_string(value);
|
|
||||||
}
|
|
||||||
} // namespace object
|
|
@@ -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
|
|
@@ -1,9 +0,0 @@
|
|||||||
#include "integers.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace object {
|
|
||||||
std::string integer::inspect() {
|
|
||||||
return std::to_string(value);
|
|
||||||
}
|
|
||||||
} // namespace object
|
|
@@ -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
|
|
@@ -1,7 +0,0 @@
|
|||||||
#include "null.hpp"
|
|
||||||
|
|
||||||
namespace object {
|
|
||||||
std::string null::inspect() {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
} // namespace object
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,84 +36,11 @@ 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) {
|
||||||
next_token();
|
next_token();
|
||||||
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
|
@@ -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
|
|
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
238
test/lexer.cpp
238
test/lexer.cpp
@@ -6,44 +6,43 @@
|
|||||||
#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;
|
std::string expectedLiteral;
|
||||||
std::string expectedLiteral;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string input = "=+(){},;";
|
|
||||||
std::istringstream ss(input);
|
|
||||||
|
|
||||||
lexer::lexer l{ss};
|
|
||||||
|
|
||||||
test tests[] = {
|
|
||||||
{token::type::ASSIGN, "="},
|
|
||||||
{token::type::PLUS, "+"},
|
|
||||||
{token::type::LPAREN, "("},
|
|
||||||
{token::type::RPAREN, ")"},
|
|
||||||
{token::type::LBRACE, "{"},
|
|
||||||
{token::type::RBRACE, "}"},
|
|
||||||
{token::type::COMMA, ","},
|
|
||||||
{token::type::SEMICOLON, ";"},
|
|
||||||
{token::type::END_OF_FILE, ""},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& t : tests) {
|
|
||||||
token::token tok = l.next_token();
|
|
||||||
REQUIRE(tok.type == t.expectedType);
|
|
||||||
REQUIRE(tok.literal == t.expectedLiteral);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_CASE("More tokens") {
|
std::string input = "=+(){},;";
|
||||||
struct test {
|
std::istringstream ss(input);
|
||||||
token::type expectedType;
|
|
||||||
std::string expectedLiteral;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::istringstream ss("let five = 5;\
|
lexer::lexer l{ss};
|
||||||
|
|
||||||
|
test tests[] = {
|
||||||
|
{token::type::ASSIGN, "="},
|
||||||
|
{token::type::PLUS, "+"},
|
||||||
|
{token::type::LPAREN, "("},
|
||||||
|
{token::type::RPAREN, ")"},
|
||||||
|
{token::type::LBRACE, "{"},
|
||||||
|
{token::type::RBRACE, "}"},
|
||||||
|
{token::type::COMMA, ","},
|
||||||
|
{token::type::SEMICOLON, ";"},
|
||||||
|
{token::type::END_OF_FILE, ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& t : tests) {
|
||||||
|
token::token tok = l.next_token();
|
||||||
|
REQUIRE(tok.type == t.expectedType);
|
||||||
|
REQUIRE(tok.literal == t.expectedLiteral);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("More tokens") {
|
||||||
|
struct test {
|
||||||
|
token::type expectedType;
|
||||||
|
std::string expectedLiteral;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::istringstream ss("let five = 5;\
|
||||||
let ten = 10;\
|
let ten = 10;\
|
||||||
let add = fn(x, y) {\
|
let add = fn(x, y) {\
|
||||||
x + y;\
|
x + y;\
|
||||||
@@ -62,102 +61,101 @@ if (5 < 10) {\
|
|||||||
10 != 9;\
|
10 != 9;\
|
||||||
");
|
");
|
||||||
|
|
||||||
lexer::lexer l{ss};
|
lexer::lexer l{ss};
|
||||||
|
|
||||||
test tests[] = {
|
test tests[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
{token::type::LET, "let"},
|
{token::type::LET, "let"},
|
||||||
{token::type::IDENTIFIER, "five"},
|
{token::type::IDENTIFIER, "five"},
|
||||||
{token::type::ASSIGN, "="},
|
{token::type::ASSIGN, "="},
|
||||||
{token::type::INT, "5"},
|
{token::type::INT, "5"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::LET, "let"},
|
{token::type::LET, "let"},
|
||||||
{token::type::IDENTIFIER, "ten"},
|
{token::type::IDENTIFIER, "ten"},
|
||||||
{token::type::ASSIGN, "="},
|
{token::type::ASSIGN, "="},
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::LET, "let"},
|
{token::type::LET, "let"},
|
||||||
{token::type::IDENTIFIER, "add"},
|
{token::type::IDENTIFIER, "add"},
|
||||||
{token::type::ASSIGN, "="},
|
{token::type::ASSIGN, "="},
|
||||||
{token::type::FUNCTION, "fn"},
|
{token::type::FUNCTION, "fn"},
|
||||||
{token::type::LPAREN, "("},
|
{token::type::LPAREN, "("},
|
||||||
{token::type::IDENTIFIER, "x"},
|
{token::type::IDENTIFIER, "x"},
|
||||||
{token::type::COMMA, ","},
|
{token::type::COMMA, ","},
|
||||||
{token::type::IDENTIFIER, "y"},
|
{token::type::IDENTIFIER, "y"},
|
||||||
{token::type::RPAREN, ")"},
|
{token::type::RPAREN, ")"},
|
||||||
{token::type::LBRACE, "{"},
|
{token::type::LBRACE, "{"},
|
||||||
{token::type::IDENTIFIER, "x"},
|
{token::type::IDENTIFIER, "x"},
|
||||||
{token::type::PLUS, "+"},
|
{token::type::PLUS, "+"},
|
||||||
{token::type::IDENTIFIER, "y"},
|
{token::type::IDENTIFIER, "y"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
{token::type::RBRACE, "}"},
|
{token::type::RBRACE, "}"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::LET, "let"},
|
{token::type::LET, "let"},
|
||||||
{token::type::IDENTIFIER, "result"},
|
{token::type::IDENTIFIER, "result"},
|
||||||
{token::type::ASSIGN, "="},
|
{token::type::ASSIGN, "="},
|
||||||
{token::type::IDENTIFIER, "add"},
|
{token::type::IDENTIFIER, "add"},
|
||||||
{token::type::LPAREN, "("},
|
{token::type::LPAREN, "("},
|
||||||
{token::type::IDENTIFIER, "five"},
|
{token::type::IDENTIFIER, "five"},
|
||||||
{token::type::COMMA, ","},
|
{token::type::COMMA, ","},
|
||||||
{token::type::IDENTIFIER, "ten"},
|
{token::type::IDENTIFIER, "ten"},
|
||||||
{token::type::RPAREN, ")"},
|
{token::type::RPAREN, ")"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::BANG, "!"},
|
{token::type::BANG, "!"},
|
||||||
{token::type::MINUS, "-"},
|
{token::type::MINUS, "-"},
|
||||||
{token::type::SLASH, "/"},
|
{token::type::SLASH, "/"},
|
||||||
{token::type::ASTERISK, "*"},
|
{token::type::ASTERISK, "*"},
|
||||||
{token::type::INT, "5"},
|
{token::type::INT, "5"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::INT, "5"},
|
{token::type::INT, "5"},
|
||||||
{token::type::LT, "<"},
|
{token::type::LT, "<"},
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::GT, ">"},
|
{token::type::GT, ">"},
|
||||||
{token::type::INT, "5"},
|
{token::type::INT, "5"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::IF, "if"},
|
{token::type::IF, "if"},
|
||||||
{token::type::LPAREN, "("},
|
{token::type::LPAREN, "("},
|
||||||
{token::type::INT, "5"},
|
{token::type::INT, "5"},
|
||||||
{token::type::LT, "<"},
|
{token::type::LT, "<"},
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::RPAREN, ")"},
|
{token::type::RPAREN, ")"},
|
||||||
{token::type::LBRACE, "{"},
|
{token::type::LBRACE, "{"},
|
||||||
|
|
||||||
{token::type::RETURN, "return"},
|
{token::type::RETURN, "return"},
|
||||||
{token::type::TRUE, "true"},
|
{token::type::TRUE, "true"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::RBRACE, "}"},
|
{token::type::RBRACE, "}"},
|
||||||
{token::type::ELSE, "else"},
|
{token::type::ELSE, "else"},
|
||||||
{token::type::LBRACE, "{"},
|
{token::type::LBRACE, "{"},
|
||||||
|
|
||||||
{token::type::RETURN, "return"},
|
{token::type::RETURN, "return"},
|
||||||
{token::type::FALSE, "false"},
|
{token::type::FALSE, "false"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::RBRACE, "}"},
|
{token::type::RBRACE, "}"},
|
||||||
|
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::EQ, "=="},
|
{token::type::EQ, "=="},
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
|
|
||||||
{token::type::INT, "10"},
|
{token::type::INT, "10"},
|
||||||
{token::type::NEQ, "!="},
|
{token::type::NEQ, "!="},
|
||||||
{token::type::INT, "9"},
|
{token::type::INT, "9"},
|
||||||
{token::type::SEMICOLON, ";"},
|
{token::type::SEMICOLON, ";"},
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& t : tests) {
|
|
||||||
token::token tok = l.next_token();
|
|
||||||
REQUIRE(tok.type == t.expectedType);
|
|
||||||
REQUIRE(tok.literal == t.expectedLiteral);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
for (const auto& t : tests) {
|
||||||
|
token::token tok = l.next_token();
|
||||||
|
REQUIRE(tok.type == t.expectedType);
|
||||||
|
REQUIRE(tok.literal == t.expectedLiteral);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
134
test/parser.cpp
Normal file
134
test/parser.cpp
Normal 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;
|
||||||
|
}
|
@@ -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))");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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));");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
Reference in New Issue
Block a user