Compare commits
76 Commits
132dc65240
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
47379c6635 | ||
|
7b01840f4d | ||
|
c943380d58 | ||
|
d1dd5f9dab | ||
|
c841cfe680 | ||
|
20b2c4a818 | ||
|
2b811a4bb9 | ||
|
a016bbaa5e | ||
|
59bff59cf7 | ||
|
98fb161bba | ||
|
6313027d07 | ||
|
4a9deff8da | ||
|
e2dfca2679 | ||
|
c6daa5c2af | ||
|
afbabd6c9e | ||
|
057d6fc9f1 | ||
|
0b2a12cef7 | ||
|
85f530f5f9 | ||
|
586a172d90 | ||
|
767a2ebcb1 | ||
|
d94bb99381 | ||
|
7c55e58e1a | ||
|
cbafb2e814 | ||
|
f0f748f7f5 | ||
|
1cda075f57 | ||
|
97b81e6771 | ||
|
870567ec6b | ||
|
34e4bfe9c9 | ||
|
9e63c923da | ||
|
86574552aa | ||
|
ab17545c93 | ||
|
e170afc840 | ||
|
aaec4cefcf | ||
|
4da5f32aea | ||
|
8c1f4e10cd | ||
|
6cd99c22fe | ||
|
c9ffeafd5f | ||
|
a1192204b1 | ||
|
7192bb318c | ||
|
552e68169f | ||
|
63f22a1b40 | ||
|
2174781b77 | ||
|
826f4de77a | ||
|
a7f5950a55 | ||
|
6e471a91d5 | ||
|
1ec438c900 | ||
|
702c34a736 | ||
|
f038d30d77 | ||
|
ed3cf748e2 | ||
|
c55c6b2b20 | ||
|
7f1cc6f45e | ||
|
c7a30a0028 | ||
|
2ff6c695f7 | ||
|
3ec6667ba3 | ||
|
c65cefe867 | ||
|
79b1aeb45f | ||
|
e3cbba08b1 | ||
|
74c555bfc0 | ||
|
96d76153d4 | ||
|
8fb0ef2be7 | ||
|
c9e21213fd | ||
|
d10e5676c1 | ||
|
83df4955d4 | ||
|
902f5a16df | ||
|
2dfff61346 | ||
|
ef624de4ef | ||
|
31cb483602 | ||
|
da2b6716b1 | ||
|
1d259e6988 | ||
|
d13f9bf9f8 | ||
|
08aacf0416 | ||
|
0b9d7d9c33 | ||
|
1638ddbfa1 | ||
|
b98424aa5f | ||
|
39eafe2360 | ||
|
7b916b2a0b |
5
Makefile
5
Makefile
@@ -3,6 +3,7 @@
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
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
|
||||||
@@ -48,7 +49,7 @@ run: $(TARGET)
|
|||||||
@$(TARGET)
|
@$(TARGET)
|
||||||
|
|
||||||
test: $(TEST_TARGET)
|
test: $(TEST_TARGET)
|
||||||
@$(TEST_TARGET) $(if $(TEST),--test-case=$(TEST))
|
@$(TEST_TARGET) $(if $(TEST),--test-case="$(TEST)") $(if $(SUBCASE),--subcase="$(SUBCASE)")
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# Link binaries
|
# Link binaries
|
||||||
@@ -70,7 +71,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) -c $< -o $@
|
$(CXX) $(CXXFLAGS) $(CXXFLAGS_TESTS) -c $< -o $@
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# Auto‐include dependencies
|
# Auto‐include dependencies
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
-I./include
|
-I./include
|
||||||
-I./src
|
-I./src
|
||||||
|
-I./test/utils
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
#include "ast.hpp"
|
|
||||||
|
|
||||||
namespace ast {
|
|
||||||
std::string program ::token_literal() const {
|
|
||||||
if (statements.size() > 0)
|
|
||||||
return statements[0]->token_literal();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
} // namespace ast
|
|
@@ -1,29 +1,20 @@
|
|||||||
#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;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct statement : node {
|
inline std::ostream& operator<<(std::ostream& os, const node& n) {
|
||||||
virtual std::string token_literal() const override = 0;
|
return os << n.str();
|
||||||
};
|
}
|
||||||
|
|
||||||
struct expression : node {
|
struct statement : node {};
|
||||||
virtual std::string token_literal() const override = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct program : public node {
|
struct expression : node {};
|
||||||
std::vector<statement*> statements;
|
|
||||||
std::string token_literal() const override;
|
|
||||||
|
|
||||||
~program() {
|
|
||||||
for (const auto& ref : statements)
|
|
||||||
delete ref;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} // namespace ast
|
} // namespace ast
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "token/token.hpp"
|
||||||
#include "token/type.hpp"
|
#include "token/type.hpp"
|
||||||
|
|
||||||
namespace ast::error {
|
namespace ast::error {
|
||||||
@@ -12,6 +13,14 @@ 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;
|
||||||
|
|
||||||
|
15
src/ast/expressions/boolean.cpp
Normal file
15
src/ast/expressions/boolean.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#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
|
17
src/ast/expressions/boolean.hpp
Normal file
17
src/ast/expressions/boolean.hpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#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
|
66
src/ast/expressions/function.cpp
Normal file
66
src/ast/expressions/function.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#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
|
32
src/ast/expressions/function.hpp
Normal file
32
src/ast/expressions/function.hpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#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,4 +8,8 @@ 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,5 +12,6 @@ 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
|
||||||
|
32
src/ast/expressions/if_then_else.cpp
Normal file
32
src/ast/expressions/if_then_else.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#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
|
20
src/ast/expressions/if_then_else.hpp
Normal file
20
src/ast/expressions/if_then_else.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#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
|
26
src/ast/expressions/infix.cpp
Normal file
26
src/ast/expressions/infix.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#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
|
18
src/ast/expressions/infix.hpp
Normal file
18
src/ast/expressions/infix.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#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
|
15
src/ast/expressions/integer.cpp
Normal file
15
src/ast/expressions/integer.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#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
|
15
src/ast/expressions/integer.hpp
Normal file
15
src/ast/expressions/integer.hpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#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
|
23
src/ast/expressions/prefix.cpp
Normal file
23
src/ast/expressions/prefix.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#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
|
17
src/ast/expressions/prefix.hpp
Normal file
17
src/ast/expressions/prefix.hpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#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
|
24
src/ast/program.cpp
Normal file
24
src/ast/program.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#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
|
23
src/ast/program.hpp
Normal file
23
src/ast/program.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#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
|
25
src/ast/statements/block.cpp
Normal file
25
src/ast/statements/block.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#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
|
20
src/ast/statements/block.hpp
Normal file
20
src/ast/statements/block.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#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
|
27
src/ast/statements/expression.cpp
Normal file
27
src/ast/statements/expression.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#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
|
18
src/ast/statements/expression.hpp
Normal file
18
src/ast/statements/expression.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#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,17 +1,36 @@
|
|||||||
#include "let.hpp"
|
#include "let.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace ast {
|
namespace ast {
|
||||||
let::let(token::token token)
|
let_stmt::let_stmt(token::token token)
|
||||||
: token(std::move(token)),
|
: token(std::move(token)),
|
||||||
name(nullptr),
|
name(nullptr),
|
||||||
value(nullptr) {}
|
value(nullptr) {}
|
||||||
|
|
||||||
std::string let::token_literal() const {
|
let_stmt::let_stmt(token::token token, identifier* name, expression* value)
|
||||||
|
: token(std::move(token)),
|
||||||
|
name(name),
|
||||||
|
value(value) {}
|
||||||
|
|
||||||
|
std::string let_stmt::token_literal() const {
|
||||||
return token.literal;
|
return token.literal;
|
||||||
}
|
}
|
||||||
|
|
||||||
let::~let() {
|
let_stmt::~let_stmt() {
|
||||||
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,15 +5,17 @@
|
|||||||
#include "token/token.hpp"
|
#include "token/token.hpp"
|
||||||
|
|
||||||
namespace ast {
|
namespace ast {
|
||||||
struct let : statement {
|
struct let_stmt : statement {
|
||||||
let(token::token token);
|
let_stmt(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();
|
~let_stmt();
|
||||||
};
|
};
|
||||||
} // namespace ast
|
} // namespace ast
|
||||||
|
33
src/ast/statements/return.cpp
Normal file
33
src/ast/statements/return.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#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
|
19
src/ast/statements/return.hpp
Normal file
19
src/ast/statements/return.hpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#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
|
41
src/evaluator/evaluator.cpp
Normal file
41
src/evaluator/evaluator.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
#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
|
8
src/evaluator/evaluator.hpp
Normal file
8
src/evaluator/evaluator.hpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ast/ast.hpp"
|
||||||
|
#include "object/object.hpp"
|
||||||
|
|
||||||
|
namespace eval {
|
||||||
|
object::object* eval(ast::node*);
|
||||||
|
} // namespace eval
|
@@ -7,6 +7,8 @@
|
|||||||
#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,10 +1,12 @@
|
|||||||
#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();
|
||||||
|
9
src/object/boolean.cpp
Normal file
9
src/object/boolean.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "boolean.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace object {
|
||||||
|
std::string boolean::inspect() {
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
} // namespace object
|
13
src/object/boolean.hpp
Normal file
13
src/object/boolean.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#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
|
9
src/object/integers.cpp
Normal file
9
src/object/integers.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "integers.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace object {
|
||||||
|
std::string integer::inspect() {
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
} // namespace object
|
13
src/object/integers.hpp
Normal file
13
src/object/integers.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#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
|
7
src/object/null.cpp
Normal file
7
src/object/null.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "null.hpp"
|
||||||
|
|
||||||
|
namespace object {
|
||||||
|
std::string null::inspect() {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
} // namespace object
|
10
src/object/null.hpp
Normal file
10
src/object/null.hpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "object.hpp"
|
||||||
|
|
||||||
|
namespace object {
|
||||||
|
struct null : object {
|
||||||
|
::object::type type = type::NULL_OBJ;
|
||||||
|
|
||||||
|
std::string inspect();
|
||||||
|
};
|
||||||
|
} // namespace object
|
13
src/object/object.hpp
Normal file
13
src/object/object.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "type.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace object {
|
||||||
|
struct object {
|
||||||
|
::object::type type;
|
||||||
|
virtual std::string inspect() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace object
|
23
src/object/type.cpp
Normal file
23
src/object/type.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#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
|
21
src/object/type.hpp
Normal file
21
src/object/type.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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,10 +1,24 @@
|
|||||||
#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),
|
||||||
@@ -12,6 +26,65 @@ 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() {
|
||||||
@@ -19,8 +92,14 @@ namespace parser {
|
|||||||
next = lexer.next_token();
|
next = lexer.next_token();
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::program* parser::parse_program() {
|
void parser::skip_until_semicolon() {
|
||||||
ast::program* p = new ast::program();
|
for (; current.type != token::type::SEMICOLON
|
||||||
|
&& 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();
|
||||||
@@ -35,10 +114,83 @@ 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) {
|
||||||
@@ -49,27 +201,6 @@ 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 "
|
||||||
@@ -77,8 +208,222 @@ 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,26 +2,72 @@
|
|||||||
|
|
||||||
#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& lexer);
|
parser(lexer::lexer&);
|
||||||
~parser();
|
~parser();
|
||||||
std::vector<ast::error::error*> errors;
|
std::vector<ast::error::error*> errors;
|
||||||
|
|
||||||
ast::program* parse_program();
|
std::unique_ptr<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::let* parse_let();
|
ast::expression* parse_expression(precedence = precedence::LOWEST);
|
||||||
|
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
|
||||||
|
24
src/parser/precedence.cpp
Normal file
24
src/parser/precedence.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#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
|
21
src/parser/precedence.hpp
Normal file
21
src/parser/precedence.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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,7 +1,10 @@
|
|||||||
#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>
|
||||||
|
|
||||||
@@ -19,11 +22,14 @@ namespace repl {
|
|||||||
std::istringstream ss(line);
|
std::istringstream ss(line);
|
||||||
|
|
||||||
lexer::lexer l{ss};
|
lexer::lexer l{ss};
|
||||||
for (token::token tok = l.next_token();
|
parser::parser p{l};
|
||||||
tok.type != token::type::END_OF_FILE;
|
std::unique_ptr<ast::program> program = p.parse_program();
|
||||||
tok = l.next_token())
|
if (!p.errors.empty()) {
|
||||||
out << tok << " ";
|
for (auto& e : p.errors)
|
||||||
out << std::endl;
|
out << e->what() << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out << program->str() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
src/utils/tracer.hpp
Normal file
30
src/utils/tracer.hpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#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
|
20
test/evaluator/integer.cpp
Normal file
20
test/evaluator/integer.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@
|
|||||||
#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;
|
||||||
@@ -159,3 +160,4 @@ if (5 < 10) {\
|
|||||||
REQUIRE(tok.literal == t.expectedLiteral);
|
REQUIRE(tok.literal == t.expectedLiteral);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
134
test/parser.cpp
134
test/parser.cpp
@@ -1,134 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
278
test/parser/expression.cpp
Normal file
278
test/parser/expression.cpp
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#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))");
|
||||||
|
}
|
||||||
|
}
|
372
test/parser/function.cpp
Normal file
372
test/parser/function.cpp
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
#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));");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
142
test/parser/if.cpp
Normal file
142
test/parser/if.cpp
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#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;}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
81
test/parser/let.cpp
Normal file
81
test/parser/let.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
test/parser/precedence.cpp
Normal file
51
test/parser/precedence.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
|
}
|
21
test/parser/return.cpp
Normal file
21
test/parser/return.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
test/program.cpp
Normal file
37
test/program.cpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#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;");
|
||||||
|
}
|
||||||
|
}
|
40
test/utils/fixtures.cpp
Normal file
40
test/utils/fixtures.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#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
|
10
test/utils/test_eval.cpp
Normal file
10
test/utils/test_eval.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#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
|
94
test/utils/test_parser.cpp
Normal file
94
test/utils/test_parser.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#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
|
91
test/utils/utils.hpp
Normal file
91
test/utils/utils.hpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#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