diff --git a/furc/include/furc/ast/declaration.hpp b/furc/include/furc/ast/declaration.hpp new file mode 100644 index 0000000..ac35bbe --- /dev/null +++ b/furc/include/furc/ast/declaration.hpp @@ -0,0 +1,74 @@ +#ifndef FURC_AST_DECLARATION_HPP +#define FURC_AST_DECLARATION_HPP + +#include "furc/ast/node.hpp" +#include "furc/ast/statement.hpp" +#include "furc/front/token.hpp" + +#include +#include + +namespace furc { +namespace ast { + +enum class declaration_node_t { + Function, + Variable, +}; + +static inline std::ostream& operator<<(std::ostream& os, declaration_node_t type) { + switch (type) { + case declaration_node_t::Function: return os << "function"; + case declaration_node_t::Variable: return os << "variable"; + } +} + +class declaration_node : public abstract_node { +public: + virtual declaration_node_t type() const = 0; +public: + virtual std::ostream& print(std::ostream& os) const = 0; + + friend std::ostream& operator<<(std::ostream& os, const declaration_node& node) { + os << node.type() << " declaration"; + return node.print(os); + } +}; + +struct function_body { + location begin, end; + std::vector> statements; +}; + +using function_body_handle = handle; + +class function_declarartion_node : public declaration_node { +public: + function_declarartion_node(front::token name) + : m_name(name) {} + + function_declarartion_node(front::token name, function_body&& body) + : m_name(name), m_body(std::move(body)) {} +public: + declaration_node_t type() const override { return declaration_node_t::Function; } +public: + std::ostream& print(std::ostream& os) const override { + os << ": " << m_name; + if (m_body.has_value()) { + os << '\n' << m_body->begin << ": begin:"; + for (const auto& entry : m_body->statements) { + os << '\n' << entry; + } + os << '\n' << m_body->end << ": end"; + } + return os; + } +private: + front::token m_name; + std::optional m_body; +}; + +} // namespace ast +} // namespace furc + +#endif // FURC_AST_DECLARATION_HPP \ No newline at end of file diff --git a/furc/include/furc/ast/node.hpp b/furc/include/furc/ast/node.hpp new file mode 100644 index 0000000..2420108 --- /dev/null +++ b/furc/include/furc/ast/node.hpp @@ -0,0 +1,44 @@ +#ifndef FURC_AST_NODE_HPP +#define FURC_AST_NODE_HPP + +#include "furc/handle.hpp" + +#include + +namespace furc { +namespace ast { + +enum class node_t { + Literal, + Expression, + Statement, + Declaration, + Program, +}; + +class node { +public: + node() = default; + virtual ~node() = default; + + node(node&&) = default; + node(const node&) = delete; + node& operator=(node&&) = default; + node& operator=(const node&) = delete; +public: + virtual node_t category() const = 0; +}; + +template +class abstract_node : public node { +public: + node_t category() const override { return Category; } +}; + +template +using node_handle = handle; + +} // namespace ast +} // namespace furc + +#endif // FURC_AST_NODE_HPP \ No newline at end of file diff --git a/furc/include/furc/ast/program.hpp b/furc/include/furc/ast/program.hpp new file mode 100644 index 0000000..64a5a70 --- /dev/null +++ b/furc/include/furc/ast/program.hpp @@ -0,0 +1,35 @@ +#ifndef FURC_AST_PROGRAM_HPP +#define FURC_AST_PROGRAM_HPP + +#include "furc/ast/declaration.hpp" +#include "furc/ast/node.hpp" + +#include +#include + +namespace furc { +namespace ast { + +class program_node : public abstract_node { +public: + program_node() {} +public: + void push(node_handle&& declaration) { m_declarations.push_back(std::move(declaration)); } + + const std::vector>& declarations() const { return m_declarations; } +public: + friend std::ostream& operator<<(std::ostream& os, const program_node& node) { + os << "program"; + for (const auto& handle : node.m_declarations) { + os << '\n' << handle; + } + return os; + } +private: + std::vector> m_declarations; +}; + +} // namespace ast +} // namespace furc + +#endif // FURC_AST_PROGRAM_HPP \ No newline at end of file diff --git a/furc/include/furc/ast/statement.hpp b/furc/include/furc/ast/statement.hpp new file mode 100644 index 0000000..806e1f0 --- /dev/null +++ b/furc/include/furc/ast/statement.hpp @@ -0,0 +1,60 @@ +#ifndef FURC_AST_STATEMENT_HPP +#define FURC_AST_STATEMENT_HPP + +#include "furc/ast/node.hpp" + +#include + +namespace furc { +namespace ast { + +enum class statement_node_t { + Declaration, + Return, +}; + +static inline std::ostream& operator<<(std::ostream& os, statement_node_t type) { + switch (type) { + case statement_node_t::Declaration: return os << "declaration"; + case statement_node_t::Return: return os << "return"; + } +} + +class statement_node : public abstract_node { +public: + virtual statement_node_t type() const = 0; +public: + virtual std::ostream& print(std::ostream& os) const = 0; + + friend std::ostream& operator<<(std::ostream& os, const statement_node& node) { + os << node.type() << " statement"; + return node.print(os); + } +}; + +class declaration_node; +class declaration_statement_node : public statement_node { +public: + declaration_statement_node(declaration_node* declaration) + : m_declaration(declaration) {} +public: + statement_node_t type() const override { return statement_node_t::Declaration; } + + std::ostream& print(std::ostream& os) const override { return os; } +private: + declaration_node* m_declaration = nullptr; +}; + +class return_statement_node : public statement_node { +public: + return_statement_node() = default; +public: + statement_node_t type() const override { return statement_node_t::Return; } + + std::ostream& print(std::ostream& os) const override { return os; } +}; + +} // namespace ast +} // namespace furc + +#endif // FURC_AST_STATEMENT_HPP \ No newline at end of file diff --git a/furc/include/furc/front/lexer.hpp b/furc/include/furc/front/lexer.hpp index 1522869..d111c16 100644 --- a/furc/include/furc/front/lexer.hpp +++ b/furc/include/furc/front/lexer.hpp @@ -8,6 +8,7 @@ namespace front { class lexer { public: + lexer() = default; lexer(std::string_view filename, std::string_view content); ~lexer() = default; @@ -16,19 +17,14 @@ public: lexer& operator=(lexer&&) = default; lexer& operator=(const lexer&) = delete; public: - token next_token(); + token_handle<> next_token(); + + bool empty() const { return m_cursor >= m_content.size(); } private: - void next(); - char get(std::size_t offset = 0) const; - void skip_spaces(); + void next(); + char get(std::size_t offset = 0) const; + void skip_spaces(); location current_location(); -private: - template - token create_token(location location, Args&&... args) { - token tok(std::forward(args)...); - tok.location = location; - return tok; - } private: std::string_view m_filename; std::string_view m_content; diff --git a/furc/include/furc/front/parser.hpp b/furc/include/furc/front/parser.hpp new file mode 100644 index 0000000..e0e75e4 --- /dev/null +++ b/furc/include/furc/front/parser.hpp @@ -0,0 +1,45 @@ +#ifndef FURC_FRONT_PARSER_HPP +#define FURC_FRONT_PARSER_HPP + +#include "furc/ast/declaration.hpp" +#include "furc/ast/node.hpp" +#include "furc/ast/program.hpp" +#include "furc/front/lexer.hpp" + +#include + +namespace furc { +namespace front { + +class parser { +public: + parser(std::string_view filename, std::string_view content); + parser(std::string_view filename); + ~parser() = default; + + parser(parser&&) = default; + parser(const parser&) = delete; + parser& operator=(parser&&) = default; + parser& operator=(const parser&) = delete; +public: + ast::node_handle parse(); +private: + ast::node_handle parse_declaration(); + ast::node_handle parse_statement(); + + ast::function_body_handle parse_body(); +private: + token_handle<> next_token(); + const token_handle<>& peek_token(); + token_handle<> eat_token(token_t type); +private: + std::string m_filename; + std::string m_content; + lexer m_lexer; + std::vector> m_peekBuffer; +}; + +} // namespace front +} // namespace furc + +#endif // FURC_FRONT_PARSER_HPP \ No newline at end of file diff --git a/furc/include/furc/front/token.hpp b/furc/include/furc/front/token.hpp index 79ea03d..964df07 100644 --- a/furc/include/furc/front/token.hpp +++ b/furc/include/furc/front/token.hpp @@ -1,7 +1,7 @@ #ifndef FURC_FRONT_TOKEN_HPP #define FURC_FRONT_TOKEN_HPP -#include "furc/diag.hpp" +#include "furc/handle.hpp" #include #include @@ -24,6 +24,8 @@ enum class token_t { Rbracket, Semicolon, Colon, + Comma, + Dot, }; static inline bool is_token_type_empty(token_t type) { @@ -45,6 +47,28 @@ static inline std::ostream& operator<<(std::ostream& os, token_t type) { case token_t::Rbracket: return os << "']'"; case token_t::Semicolon: return os << "';'"; case token_t::Colon: return os << "':'"; + case token_t::Comma: return os << "','"; + case token_t::Dot: return os << "'.'"; + } +} + +static inline std::string operator+(const std::string& str, token_t type) { + switch (type) { + case token_t::None: return str + "none"; + case token_t::Error: return str + "error"; + case token_t::Identifier: return str + "identifier"; + case token_t::Keyword: return str + "keyword"; + case token_t::Integer: return str + "integer"; + case token_t::Lparen: return str + "'('"; + case token_t::Rparen: return str + "')'"; + case token_t::Lbrace: return str + "'{'"; + case token_t::Rbrace: return str + "'}'"; + case token_t::Lbracket: return str + "'['"; + case token_t::Rbracket: return str + "']'"; + case token_t::Semicolon: return str + "';'"; + case token_t::Colon: return str + "':'"; + case token_t::Comma: return str + "','"; + case token_t::Dot: return str + "'.'"; } } @@ -66,12 +90,27 @@ enum class keyword_token { Return, }; +static inline std::ostream& operator<<(std::ostream& os, keyword_token keyword) { + switch (keyword) { + case keyword_token::None: return os << "none"; + case keyword_token::Func: return os << "func"; + case keyword_token::Return: return os << "return"; + } +} + +static inline std::string operator+(const std::string& str, keyword_token keyword) { + switch (keyword) { + case keyword_token::None: return str + "none"; + case keyword_token::Func: return str + "func"; + case keyword_token::Return: return str + "return"; + } +} + struct token { token_t type = token_t::None; error_token error = error_token::None; keyword_token keyword = keyword_token::None; std::string_view value; - location location; token() = default; @@ -86,7 +125,7 @@ struct token { }; static inline std::ostream& operator<<(std::ostream& os, const token& token) { - os << token.location << ": " << token.type; + os << token.type; switch (token.type) { case token_t::Error: { os << ": " << token.error; @@ -100,6 +139,9 @@ static inline std::ostream& operator<<(std::ostream& os, const token& token) { } } +template +using token_handle = handle; + } // namespace front } // namespace furc diff --git a/furc/include/furc/handle.hpp b/furc/include/furc/handle.hpp new file mode 100644 index 0000000..9cb831e --- /dev/null +++ b/furc/include/furc/handle.hpp @@ -0,0 +1,193 @@ +#ifndef FURC_HANDLE_HPP +#define FURC_HANDLE_HPP + +#include "furc/diag.hpp" + +#include +#include +#include +#include +#include + +namespace furc { + +template +class handle; + +template +class handle { + template + friend class handle; + + friend std::ostream& operator<<(std::ostream& os, const handle& result); +public: + using value_type = std::remove_reference_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = T&; + using const_reference = const T&; +public: + handle(location location, value_type&& value) + : m_location(location), m_value(std::move(value)) {} + + template >> + handle(location location, Args&&... args) + : m_location(location), m_value(value_type(std::forward(args)...)) {} + + handle(location location, Error&& error) + : m_location(location), m_error(std::move(error)) {} + + template + handle(const handle& error) + : m_location(error.location()), m_error(error) {} + + ~handle() = default; + + handle(handle&& other) noexcept + : m_location(other.m_location), m_value(std::move(other.m_value)), m_error(std::move(other.m_error)) { + other.m_value.reset(); + } + + handle(const handle&) = delete; + + handle& operator=(handle&& other) noexcept { + if (this == &other) return *this; + m_location = other.m_location; + m_value = other.m_value; + m_error = std::move(other.m_error); + other.m_value.reset(); + return *this; + } + + handle& operator=(const handle&) = delete; +public: + location location() const { return m_location; } + + bool present() const { return m_value.has_value(); } + bool error() const { return !m_value.has_value(); } + + value_type move() { + value_type value = *m_value; + m_value.reset(); + return value; + } + + operator reference() { return *m_value; } + operator const_reference() const { return *m_value; } + operator Error() const { return m_error; } + + reference operator*() { return m_value; } + const_reference operator*() const { return m_value; } + pointer operator->() { return &*m_value; } + const_pointer operator->() const { return &*m_value; } +public: + friend std::ostream& operator<<(std::ostream& os, const handle& result) { + os << result.m_location << ": "; + if (result.m_value.has_value()) { + os << *result.m_value; + } else { + os << "ERROR: " << result.m_error; + } + return os; + } +private: + struct location m_location; + std::optional m_value = {}; + Error m_error; +}; + +template +class handle { + template + friend class handle; + + friend std::ostream& operator<<(std::ostream& os, const handle& result); +public: + using value_type = T; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = T&; + using const_reference = const T&; +public: + template >> + handle(location location, std::shared_ptr value) + : m_location(location), m_value(value) {} + + template >> + handle(location location, Args&&... args) + : m_location(location), m_value(std::make_shared(std::forward(args)...)) {} + + handle(location location, Error&& error) + : m_location(location), m_error(std::move(error)) {} + + template + handle(const handle& error) + : m_location(error.location()), m_error(error) {} + + ~handle() = default; + + handle(handle&& other) noexcept + : m_location(other.m_location), m_value(std::move(other.m_value)), m_error(std::move(other.m_error)) { + other.m_value = nullptr; + } + + template >> + handle(handle&& other) noexcept // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) + : m_location(other.m_location), m_value(std::move(other.m_value)), m_error(std::move(other.m_error)) { + other.m_value = nullptr; + } + + handle(const handle& other) + : m_location(other.m_location), m_value(other.m_value), m_error(other.m_error) {} + + handle& operator=(handle&& other) noexcept { + if (this == &other) return *this; + m_location = other.m_location; + m_value = std::move(other.m_value); + m_error = std::move(other.m_error); + other.m_value = nullptr; + return *this; + } + + handle& operator=(const handle& other) { + if (this == &other) return *this; + m_location = other.m_location; + m_value = other.m_value; + m_error = other.m_error; + return *this; + } +public: + location location() const { return m_location; } + + bool present() const { return m_value != nullptr; } + bool error() const { return m_value == nullptr; } + + std::shared_ptr shared() { return m_value; } + + operator reference() { return *m_value; } + operator const_reference() const { return *m_value; } + operator Error() const { return m_error; } + + reference operator*() { return *m_value; } + const_reference operator*() const { return *m_value; } + pointer operator->() { return m_value; } + const_pointer operator->() const { return m_value; } +public: + friend std::ostream& operator<<(std::ostream& os, const handle& result) { + os << result.m_location << ": "; + if (result.m_value != nullptr) { + os << *result.m_value; + } else { + os << "ERROR: " << result.m_error; + } + return os; + } +private: + struct location m_location; + std::shared_ptr m_value = nullptr; + Error m_error; +}; + +} // namespace furc + +#endif // FURC_HANDLE_HPP \ No newline at end of file diff --git a/furc/src/back/lexer.cpp b/furc/src/front/lexer.cpp similarity index 56% rename from furc/src/back/lexer.cpp rename to furc/src/front/lexer.cpp index cfad2b4..5c547f2 100644 --- a/furc/src/back/lexer.cpp +++ b/furc/src/front/lexer.cpp @@ -8,22 +8,24 @@ namespace furc::front { lexer::lexer(std::string_view filename, std::string_view content) : m_filename(filename), m_content(content) {} -token lexer::next_token() { +token_handle<> lexer::next_token() { skip_spaces(); location location = current_location(); char ch = get(); switch (ch) { - case '(': return create_token(location, token_t::Lparen, m_content.substr(m_cursor++, 1)); - case ')': return create_token(location, token_t::Rparen, m_content.substr(m_cursor++, 1)); - case '{': return create_token(location, token_t::Lbrace, m_content.substr(m_cursor++, 1)); - case '}': return create_token(location, token_t::Rbrace, m_content.substr(m_cursor++, 1)); - case '[': return create_token(location, token_t::Lbracket, m_content.substr(m_cursor++, 1)); - case ']': return create_token(location, token_t::Rbracket, m_content.substr(m_cursor++, 1)); - case ';': return create_token(location, token_t::Semicolon, m_content.substr(m_cursor++, 1)); - case ':': return create_token(location, token_t::Colon, m_content.substr(m_cursor++, 1)); - case std::char_traits::eof(): return create_token(location, token_t::None); + case '(': return { location, token_t::Lparen, m_content.substr(m_cursor++, 1) }; + case ')': return { location, token_t::Rparen, m_content.substr(m_cursor++, 1) }; + case '{': return { location, token_t::Lbrace, m_content.substr(m_cursor++, 1) }; + case '}': return { location, token_t::Rbrace, m_content.substr(m_cursor++, 1) }; + case '[': return { location, token_t::Lbracket, m_content.substr(m_cursor++, 1) }; + case ']': return { location, token_t::Rbracket, m_content.substr(m_cursor++, 1) }; + case ';': return { location, token_t::Semicolon, m_content.substr(m_cursor++, 1) }; + case ':': return { location, token_t::Colon, m_content.substr(m_cursor++, 1) }; + case ',': return { location, token_t::Comma, m_content.substr(m_cursor++, 1) }; + case '.': return { location, token_t::Dot, m_content.substr(m_cursor++, 1) }; + case std::char_traits::eof(): return { location, token_t::None }; default: { if (std::isdigit(ch) != 0) {} @@ -39,12 +41,11 @@ token lexer::next_token() { { "return", keyword_token::Return }, }; - if (auto it = s_keywords.find(value); it != s_keywords.end()) - return create_token(location, it->second, value); - return create_token(location, token_t::Identifier, value); + if (auto it = s_keywords.find(value); it != s_keywords.end()) return { location, it->second, value }; + return { location, token_t::Identifier, value }; } - return create_token(location, error_token::UnexpectedCharacter, m_content.substr(m_cursor, 1)); + return { location, error_token::UnexpectedCharacter, m_content.substr(m_cursor, 1) }; } } } diff --git a/furc/src/front/parser.cpp b/furc/src/front/parser.cpp new file mode 100644 index 0000000..ccab912 --- /dev/null +++ b/furc/src/front/parser.cpp @@ -0,0 +1,166 @@ +#include "furc/front/parser.hpp" + +#include "furc/ast/declaration.hpp" + +#include +#include +#include + +namespace furc::front { + +using namespace std::string_literals; + +parser::parser(std::string_view filename, std::string_view content) + : m_filename(filename), m_content(content), m_lexer(m_filename, m_content) {} + +parser::parser(std::string_view filename) + : m_filename(filename) { + std::ifstream file(m_filename, std::ios_base::binary | std::ios_base::ate); + if (!file.is_open()) throw std::runtime_error("failed to open file "s.append(m_filename)); + std::streampos size = file.tellg(); + file.seekg(0); + + m_content.resize(size); + file.read(m_content.data(), size); + m_lexer = { filename, m_content }; +} + +ast::node_handle parser::parse() { + auto program = std::make_shared(); + + while (!m_lexer.empty()) { + program->push(std::move(parse_declaration())); + } + + return { location{ m_filename, 0, 0 }, program }; +} + +ast::node_handle parser::parse_declaration() { + token_handle<> first = next_token(); + switch (first->type) { + case token_t::Keyword: { + switch (first->keyword) { + case keyword_token::Func: { + token_handle<> name = eat_token(token_t::Identifier); + if (name.error()) return { name.location(), name }; + + auto tok = eat_token(token_t::Lparen); + if (tok.error()) return tok; + tok = eat_token(token_t::Rparen); + if (tok.error()) return tok; + + const auto& peek = peek_token(); + if (peek.error()) return peek; + switch (peek->type) { + case token_t::Lbrace: { + ast::function_body_handle body = parse_body(); + if (body.error()) return body; + return ast::node_handle{ first.location(), + name, + std::move(body.move()) }; + } + case token_t::Semicolon: { + m_peekBuffer.clear(); + return ast::node_handle{ first.location(), name }; + } + case token_t::None: + case token_t::Error: + case token_t::Identifier: + case token_t::Keyword: + case token_t::Integer: + case token_t::Lparen: + case token_t::Rparen: + case token_t::Rbrace: + case token_t::Lbracket: + case token_t::Rbracket: + case token_t::Colon: + case token_t::Comma: + case token_t::Dot: return { tok.location(), "unexpected token "s + tok->type }; + } + } + case keyword_token::Return: + case keyword_token::None: + default: return { first.location(), "unexpected keyword "s + first->keyword }; + } + } + case token_t::None: + case token_t::Error: + case token_t::Identifier: + case token_t::Integer: + case token_t::Lparen: + case token_t::Rparen: + case token_t::Lbrace: + case token_t::Rbrace: + case token_t::Lbracket: + case token_t::Rbracket: + case token_t::Semicolon: + case token_t::Colon: + default: { + return { first.location(), "unexpected token "s + first->type }; + } + } +} + +ast::node_handle parser::parse_statement() { + const auto& tok = peek_token(); + if (tok.error()) return tok; + switch (tok->type) { + case token_t::Keyword: { + next_token(); + auto err = eat_token(token_t::Semicolon); + if (err.error()) return err; + + return ast::node_handle{ tok.location() }; + } + default: break; + } + + auto declaration = parse_declaration(); + if (declaration.error()) + return { declaration.location(), "unexpected token "s + tok->type + ", expected statement" }; + return ast::node_handle{ declaration.location(), declaration }; +} + +ast::function_body_handle parser::parse_body() { + ast::function_body body; + + token_handle<> begin = eat_token(token_t::Lbrace); + if (begin.error()) return begin; + body.begin = begin.location(); + + while (!peek_token().error() && peek_token()->type != token_t::Rbrace) { + body.statements.push_back(parse_statement()); + } + + token_handle<> end = eat_token(token_t::Rbrace); + if (end.error()) return end; + body.end = end.location(); + + return { begin.location(), body }; +} + +token_handle<> parser::next_token() { + if (!m_peekBuffer.empty()) { + token_handle<> token = std::move(m_peekBuffer.back()); + m_peekBuffer.pop_back(); + return token; + } + return m_lexer.next_token(); +} + +const token_handle<>& parser::peek_token() { + if (m_peekBuffer.empty()) { + token_handle<> token = m_lexer.next_token(); + return m_peekBuffer.emplace_back(std::move(token)); + } + return m_peekBuffer.front(); +} + +token_handle<> parser::eat_token(token_t type) { + token_handle<> token = next_token(); + if (!token.present()) return token; + if (token->type != type) return { token.location(), "unexpected token "s + token->type + ", expected " + type }; + return token; +} + +} // namespace furc::front \ No newline at end of file diff --git a/furc/src/main.cpp b/furc/src/main.cpp index 00d1ffc..33330c2 100644 --- a/furc/src/main.cpp +++ b/furc/src/main.cpp @@ -1,15 +1,10 @@ -#include "furc/front/lexer.hpp" +#include "furc/front/parser.hpp" #include int main(void) { - furc::front::lexer lexer("", "func main() {\n return 0;\n}"); - - furc::front::token token = {}; - while (!furc::front::is_token_type_empty((token = lexer.next_token()).type)) { - std::cout << token << '\n'; - } - if (token.type == furc::front::token_t::Error) std::cout << token << '\n'; + furc::front::parser parser("", "func main() {\n return;\n}"); + std::cout << parser.parse() << '\n'; return 0; } \ No newline at end of file