2 files modified
8 files renamed
17 files added
12 files deleted
| | |
| | | configure_file("test_scripts/test1.vs" "test_scripts/test1.vs" @ONLY) |
| | | configure_file("test_scripts/test2.vs" "test_scripts/test2.vs" @ONLY) |
| | | |
| | | include_directories(${CMAKE_BINARY_DIR}/include ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include) |
| | | include_directories(${CMAKE_BINARY_DIR}/include ${CMAKE_SOURCE_DIR}/src) |
| | | |
| | | |
| | | # LIBRARY TARGET |
| | | add_library(voidscript |
| | | src/ScriptInterpreter.cpp |
| | | src/Lexer.cpp |
| | | src/Parser/Parser.cpp |
| | | src/Lexer/Lexer.cpp |
| | | ) |
| | | |
| | | install(TARGETS voidscript DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT "lib") |
| | |
| | | #include <filesystem> |
| | | #include <fstream> |
| | | #include <iostream> |
| | | #include <unordered_map> |
| | | |
| | | #include "Builtins/PrintModule.hpp" |
| | | #include "Builtins/SleepModule.hpp" |
| | | #include "ScriptInterpreter.hpp" |
| | | #include "options.h" |
| | | #include "VoidScript.hpp" |
| | | |
| | | const std::unordered_map<std::string, std::string> params = { |
| | | { "--help", "Print this help message" }, |
| | |
| | | |
| | | const std::string filename = std::filesystem::canonical(file).string(); |
| | | |
| | | try { |
| | | std::ifstream input(filename); |
| | | if (!input.is_open()) { |
| | | std::cerr << "Error: Could not open file " << filename << "\n"; |
| | | return 1; |
| | | } |
| | | |
| | | std::string content((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); |
| | | ScriptInterpreter interp; |
| | | interp.registerModule("print", std::make_shared<PrintFunction>()); |
| | | interp.registerModule("sleep", std::make_shared<SleepFunction>()); |
| | | interp.executeScript(content, filename, "DEFAULT", false); |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "Parser error: " << e.what() << "\n"; |
| | | return 1; |
| | | } |
| | | VoidScript voidscript(filename); |
| | | return voidscript.run(); |
| | | |
| | | return 0; |
| | | } |
| New file |
| | |
| | | #ifndef INTERPRETER_FUNCTION_EXECUTOR_HPP |
| | | #define INTERPRETER_FUNCTION_EXECUTOR_HPP |
| | | |
| | | namespace Interpreter { |
| | | struct StatementNode { |
| | | virtual ~StatementNode() = default; |
| | | virtual void interpret(class Interpreter & interpreter) const = 0; |
| | | }; |
| | | |
| | | // Kifejezés (csak int literál most) |
| | | struct ExpressionNode { |
| | | virtual ~ExpressionNode() = default; |
| | | virtual int evaluate(class Interpreter & interpreter) const = 0; |
| | | }; |
| | | |
| | | } // namespace Interpreter |
| | | #endif // INTERPRETER_FUNCTION_EXECUTOR_HPP |
| New file |
| | |
| | | #ifndef INTERPRETER_HPP |
| | | #define INTERPRETER_HPP |
| | | #include <memory> |
| | | #include <utility> |
| | | #include <vector> |
| | | |
| | | #include "Interpreter/Operation.hpp" |
| | | #include "Interpreter/OperationContainer.hpp" |
| | | #include "Symbols/SymbolContainer.hpp" |
| | | |
| | | namespace Interpreter { |
| | | |
| | | class Interpreter { |
| | | private: |
| | | std::shared_ptr<Symbols::SymbolContainer> symbol_container_; |
| | | const OperationContainer & operations_conatiner_; |
| | | |
| | | void setVariable(const std::string & name, int value) { |
| | | //symbol_container_->setVariable(name, value); |
| | | } |
| | | public: |
| | | // Constructor takes the populated symbol container from the parser |
| | | Interpreter(std::shared_ptr<Symbols::SymbolContainer> symbols, const OperationContainer & operations) : |
| | | symbol_container_(std::move(symbols)), |
| | | operations_conatiner_(operations) {} |
| | | |
| | | void run(const std::vector<Operation> & ops) { |
| | | for (const auto & op : ops) { |
| | | switch (op.type) { |
| | | case OperationType::Assignment: |
| | | { |
| | | int value = op.expression->evaluate(*this); |
| | | setVariable(op.targetVariable, value); |
| | | break; |
| | | } |
| | | |
| | | case OperationType::Expression: |
| | | { |
| | | op.expression->evaluate(*this); // csak side effect miatt |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | }; // class Interpreter |
| | | |
| | | } // namespace Interpreter |
| | | #endif // INTERPRETER_HPP |
| New file |
| | |
| | | #ifndef INTERPRETER_OPERATION_HPP |
| | | #define INTERPRETER_OPERATION_HPP |
| | | |
| | | #include <cstdint> |
| | | |
| | | #include <memory> |
| | | #include <string> |
| | | |
| | | #include "ExpressionNode.hpp" |
| | | |
| | | namespace Interpreter { |
| | | enum OperationType : std::uint8_t { |
| | | Assignment, |
| | | Expression, |
| | | |
| | | }; |
| | | |
| | | struct Operation { |
| | | OperationType type; |
| | | |
| | | // Általános mezők |
| | | std::string targetVariable; |
| | | std::unique_ptr<ExpressionNode> expression; |
| | | |
| | | Operation(OperationType t, std::string var, std::unique_ptr<ExpressionNode> expr) |
| | | : type(t), targetVariable(std::move(var)), expression(std::move(expr)) {} |
| | | }; |
| | | |
| | | |
| | | }; // namespace Interpreter |
| | | #endif |
| New file |
| | |
| | | #ifndef INTERPRETER_OPERATION_CONTAINER_HPP |
| | | #define INTERPRETER_OPERATION_CONTAINER_HPP |
| | | |
| | | #include <vector> |
| | | |
| | | #include "Interpreter/Operation.hpp" |
| | | |
| | | namespace Interpreter { |
| | | using OperationContainer = std::vector<Operation>; |
| | | }; // namespace Interpreter |
| | | |
| | | #endif // INTERPRETER_OPERATION_CONTAINER_HPP |
| New file |
| | |
| | | #include "Lexer/Lexer.hpp" |
| | | |
| | | namespace Lexer { |
| | | const std::vector<std::string> Lexer::Lexer::OPERATOR_RELATIONAL = { "==", "!=", "<", ">", "<=", ">=" }; |
| | | const std::vector<std::string> Lexer::Lexer::OPERATOR_INCREMENT = { "++", "--" }; |
| | | const std::vector<std::string> Lexer::Lexer::OPERATOR_ASSIGNMENT = { "=", "+=", "-=", "*=", "/=", "%=" }; |
| | | const std::vector<std::string> Lexer::Lexer::OPERATOR_LOGICAL = { "&&", "||" }; |
| | | |
| | | const std::vector<std::string> Lexer::Lexer::OPERATOR_ARITHMETIC = { "+", "-", "*", "/", "%" }; |
| | | const std::vector<std::string> Lexer::Lexer::PUNCTUATION = { "(", ")", "{", "}", "[", "]", ",", ";" }; |
| | | |
| | | |
| | | |
| | | }; // namespace Lexer |
| New file |
| | |
| | | #ifndef LEXER_HPP |
| | | #define LEXER_HPP |
| | | |
| | | #include <algorithm> // std::find_if |
| | | #include <cctype> // <<< Hozzáadva |
| | | #include <string> |
| | | #include <string_view> // <<< Hozzáadva |
| | | #include <unordered_map> |
| | | #include <vector> |
| | | |
| | | #include "Token.hpp" // Feltételezzük, hogy ez a fenti Token.hpp |
| | | |
| | | namespace Lexer { |
| | | class Lexer { |
| | | public: |
| | | Lexer() { |
| | | for (const auto & vecRef : |
| | | { std::cref(OPERATOR_ARITHMETIC), std::cref(OPERATOR_RELATIONAL), std::cref(OPERATOR_INCREMENT), |
| | | std::cref(OPERATOR_ASSIGNMENT), std::cref(OPERATOR_LOGICAL), std::cref(PUNCTUATION) }) { |
| | | for (const auto & str : vecRef.get()) { |
| | | operators_ += str; |
| | | } |
| | | } |
| | | |
| | | operators_ += "$"; |
| | | } |
| | | |
| | | void addNamespaceInput(const std::string & ns, const std::string & input) { |
| | | inputs_[ns] = input; |
| | | positions_[ns] = 0; |
| | | line_numbers_[ns] = 1; |
| | | column_numbers_[ns] = 1; |
| | | } |
| | | |
| | | void setNamespace(const std::string & ns) { current_namespace_ = ns; } |
| | | |
| | | std::vector<Tokens::Token> tokenizeNamespace(const std::string & ns) { |
| | | if (inputs_.find(ns) == inputs_.end()) { |
| | | return {}; |
| | | } |
| | | |
| | | setNamespace(ns); |
| | | std::vector<Tokens::Token> tokens; |
| | | Tokens::Token token; |
| | | do { |
| | | token = nextToken(); |
| | | tokens.push_back(token); |
| | | } while (token.type != Tokens::Type::END_OF_FILE); |
| | | |
| | | tokens_[ns] = tokens; |
| | | return tokens; |
| | | } |
| | | |
| | | std::vector<Tokens::Token> getTokens(const std::string & ns) const { |
| | | auto it = tokens_.find(ns); |
| | | if (it != tokens_.end()) { |
| | | return it->second; |
| | | } |
| | | return {}; |
| | | } |
| | | |
| | | void setKeyWords(const std::unordered_map<std::string, Tokens::Type> & new_keywords) { keywords = new_keywords; } |
| | | |
| | | Tokens::Token nextToken() { |
| | | skipWhitespaceAndComments(); |
| | | size_t start = pos(); |
| | | |
| | | if (isAtEnd()) { |
| | | return createToken(Tokens::Type::END_OF_FILE, start, start); |
| | | } |
| | | |
| | | char c = peek(); |
| | | if (isalpha(c) || c == '_') { |
| | | return matchIdentifierOrKeyword(start); |
| | | } |
| | | if (isdigit(c) || (isdigit(c) && peek(1) == '.')|| (c == '.' && isdigit(peek(1)))) { |
| | | return matchNumber(start); |
| | | } |
| | | if (c == '"' || c == '\'') { |
| | | return matchStringLiteral(start); |
| | | } |
| | | if (operators_.find(c) != std::string_view::npos) { |
| | | return matchOperatorOrPunctuation(start); |
| | | } |
| | | |
| | | advance(); |
| | | return createToken(Tokens::Type::UNKNOWN, start, pos()); |
| | | } |
| | | |
| | | |
| | | private: |
| | | std::unordered_map<std::string, std::string> inputs_; |
| | | std::unordered_map<std::string, std::vector<Tokens::Token>> tokens_; |
| | | std::unordered_map<std::string, size_t> positions_; |
| | | std::unordered_map<std::string, int> line_numbers_; |
| | | std::unordered_map<std::string, int> column_numbers_; |
| | | |
| | | std::string operators_; |
| | | std::string current_namespace_; |
| | | std::unordered_map<std::string, Tokens::Type> keywords; |
| | | |
| | | // two chars |
| | | static const std::vector<std::string> OPERATOR_RELATIONAL; |
| | | static const std::vector<std::string> OPERATOR_INCREMENT; |
| | | static const std::vector<std::string> OPERATOR_ASSIGNMENT; |
| | | static const std::vector<std::string> OPERATOR_LOGICAL; |
| | | |
| | | // one char |
| | | static const std::vector<std::string> OPERATOR_ARITHMETIC; |
| | | static const std::vector<std::string> PUNCTUATION; |
| | | |
| | | const std::string & input() const { return inputs_.at(current_namespace_); } |
| | | |
| | | size_t & pos() { return positions_[current_namespace_]; } |
| | | |
| | | int & line() { return line_numbers_[current_namespace_]; } |
| | | |
| | | int & col() { return column_numbers_[current_namespace_]; } |
| | | |
| | | Tokens::Token createToken(Tokens::Type type, size_t start, size_t end, const std::string & value = "") { |
| | | Tokens::Token token; |
| | | token.type = type; |
| | | token.start_pos = start; |
| | | token.end_pos = end; |
| | | token.line_number = line(); |
| | | token.column_number = col(); |
| | | if (start <= end && end <= input().length()) { |
| | | token.lexeme = std::string_view(input()).substr(start, end - start); |
| | | token.value = value.empty() ? std::string(token.lexeme) : value; |
| | | } |
| | | return token; |
| | | } |
| | | |
| | | // -------------------------------------- |
| | | |
| | | char peek(size_t offset = 0) const { |
| | | const auto & in = inputs_.at(current_namespace_); |
| | | size_t cp = positions_.at(current_namespace_); |
| | | if (cp + offset >= in.length()) { |
| | | return '\0'; |
| | | } |
| | | return in[cp + offset]; |
| | | } |
| | | |
| | | char advance() { |
| | | char c = peek(); |
| | | pos()++; |
| | | if (c == '\n') { |
| | | line()++; |
| | | col() = 1; |
| | | } else { |
| | | col()++; |
| | | } |
| | | return c; |
| | | } |
| | | |
| | | bool isAtEnd() const { return positions_.at(current_namespace_) >= inputs_.at(current_namespace_).length(); } |
| | | |
| | | bool isComment(const char current_char) const { |
| | | return (current_char == '/' && peek(1) == '/' || current_char == '#'); |
| | | } |
| | | |
| | | void skipWhitespaceAndComments() { |
| | | while (!isAtEnd()) { |
| | | char c = peek(); |
| | | if (isspace(c)) { |
| | | advance(); |
| | | } else if ((c == '/' && peek(1) == '/') || c == '#') { |
| | | while (!isAtEnd() && peek() != '\n') { |
| | | advance(); |
| | | } |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | Tokens::Token matchIdentifierOrKeyword(size_t start_pos, Tokens::Type type = Tokens::Type::IDENTIFIER) { |
| | | while (!isAtEnd() && (isalnum(peek()) || peek() == '_')) { |
| | | advance(); |
| | | } |
| | | size_t end = pos(); |
| | | std::string value = input().substr(start_pos, end - start_pos); |
| | | if (value.empty()) { |
| | | return createToken(Tokens::Type::UNKNOWN, start_pos, end); |
| | | } |
| | | |
| | | if (type == Tokens::Type::IDENTIFIER) { |
| | | auto it = keywords.find(value); |
| | | if (it != keywords.end()) { |
| | | return createToken(it->second, start_pos, end); |
| | | } |
| | | } |
| | | return createToken(type, start_pos, end); |
| | | } |
| | | |
| | | Tokens::Token matchNumber(size_t start_pos) { |
| | | bool has_dot = false; |
| | | |
| | | while (!isAtEnd()) { |
| | | if (isdigit(peek())) { |
| | | advance(); |
| | | } else if (!has_dot && peek() == '.' && isdigit(peek(1))) { |
| | | has_dot = true; |
| | | advance(); // a pont |
| | | advance(); // az első számjegy a pont után |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | size_t end = pos(); |
| | | return createToken(Tokens::Type::NUMBER, start_pos, end); |
| | | } |
| | | |
| | | Tokens::Token matchStringLiteral(size_t start_pos) { |
| | | char opening_quote = peek(); |
| | | advance(); // Skip opening quote |
| | | std::string value; |
| | | bool unterminated = false; |
| | | |
| | | while (!isAtEnd()) { |
| | | char c = peek(); |
| | | if (c == opening_quote) { |
| | | advance(); |
| | | break; |
| | | } |
| | | if (c == '\\') { |
| | | advance(); |
| | | char e = advance(); |
| | | switch (e) { |
| | | case 'n': |
| | | value += '\n'; |
| | | break; |
| | | case 't': |
| | | value += '\t'; |
| | | break; |
| | | case '"': |
| | | value += opening_quote; |
| | | break; |
| | | case '\\': |
| | | value += '\\'; |
| | | break; |
| | | default: |
| | | value += e; |
| | | break; |
| | | } |
| | | } else { |
| | | value += advance(); |
| | | } |
| | | } |
| | | |
| | | size_t end = pos(); |
| | | if (unterminated) { |
| | | return createToken(Tokens::Type::UNKNOWN, start_pos, end, input().substr(start_pos, end - start_pos)); |
| | | } |
| | | return createToken(Tokens::Type::STRING_LITERAL, start_pos, end, value); |
| | | } |
| | | |
| | | Tokens::Token matchOperatorOrPunctuation(size_t start_pos) { |
| | | char first_char = advance(); // Első karakter elfogyasztása |
| | | |
| | | if (!isAtEnd()) { |
| | | char second_char = peek(0); // Következő karakter megnézése |
| | | std::string two_chars_str{ first_char, second_char }; |
| | | |
| | | const std::vector<std::pair<const std::vector<std::string> *, Tokens::Type>> two_char_op_types = { |
| | | { &OPERATOR_RELATIONAL, Tokens::Type::OPERATOR_RELATIONAL }, |
| | | { &OPERATOR_INCREMENT, Tokens::Type::OPERATOR_INCREMENT }, |
| | | { &OPERATOR_ASSIGNMENT, Tokens::Type::OPERATOR_ASSIGNMENT }, |
| | | { &OPERATOR_LOGICAL, Tokens::Type::OPERATOR_LOGICAL } |
| | | }; |
| | | |
| | | for (const auto & [vec_ptr, type] : two_char_op_types) { |
| | | if (matchFromVector(*vec_ptr, two_chars_str)) { |
| | | advance(); // Második karakter elfogyasztása |
| | | size_t end_pos = pos(); |
| | | return createToken(type, start_pos, end_pos); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Egykarakteres operátor vagy írásjel |
| | | std::string single_char_str(1, first_char); |
| | | |
| | | if (single_char_str == "$") { |
| | | if (isalpha(peek(0)) || peek(0) == '_') { |
| | | return matchIdentifierOrKeyword(start_pos, Tokens::Type::VARIABLE_IDENTIFIER); |
| | | } |
| | | } |
| | | |
| | | const std::vector<std::pair<const std::vector<std::string> *, Tokens::Type>> one_char_op_types = { |
| | | { &OPERATOR_ARITHMETIC, Tokens::Type::OPERATOR_ARITHMETIC }, |
| | | { &OPERATOR_ASSIGNMENT, Tokens::Type::OPERATOR_ASSIGNMENT }, // "=" itt van! |
| | | { &PUNCTUATION, Tokens::Type::PUNCTUATION } |
| | | }; |
| | | |
| | | for (const auto & [vec_ptr, type] : one_char_op_types) { |
| | | if (matchFromVector(*vec_ptr, single_char_str)) { |
| | | size_t end_pos = pos(); |
| | | return createToken(type, start_pos, end_pos); |
| | | } |
| | | } |
| | | |
| | | size_t end_pos = pos(); |
| | | return createToken(Tokens::Type::UNKNOWN, start_pos, end_pos); |
| | | } |
| | | |
| | | static bool matchFromVector(const std::vector<std::string> & vec, const std::string & value) { |
| | | return std::find(vec.begin(), vec.end(), value) != vec.end(); |
| | | } |
| | | |
| | | }; // class Lexer |
| | | |
| | | }; // namespace Lexer |
| | | #endif // LEXER_HPP |
| New file |
| | |
| | | #ifndef TOKEN_HPP |
| | | #define TOKEN_HPP |
| | | |
| | | #include <iostream> |
| | | #include <string> // <<< Hozzáadva |
| | | #include <string_view> // <<< Hozzáadva |
| | | |
| | | #include "Lexer/TokenType.hpp" // Feltételezzük, hogy ez definiálja Type-ot és TypeToString-ot |
| | | |
| | | namespace Lexer::Tokens { |
| | | |
| | | struct Token { |
| | | Lexer::Tokens::Type type; |
| | | std::string value; // A token feldolgozott értéke |
| | | std::string_view lexeme; // A nyers szövegrész az eredeti stringből |
| | | size_t start_pos; // Kezdő index |
| | | size_t end_pos; // Vég index (exclusive) |
| | | int line_number; |
| | | int column_number; |
| | | |
| | | // Módosított print metódus a lexeme kiírásával |
| | | void print() const { |
| | | std::cout << "Token { Type: " << Lexer::Tokens::TypeToString(type) << ", Value: \"" << value << "\"" |
| | | << ", Pos: [" << start_pos << ", " << end_pos |
| | | << ")" |
| | | // Lexeme kiírása (stringgé konvertálva a biztonság kedvéért, ha pl. null lenne) |
| | | << ", Lexeme: \"" << std::string(lexeme) << "\"" |
| | | << " }" << '\n'; |
| | | } |
| | | }; |
| | | |
| | | inline bool operator==(const Token & lhs, const Token & rhs) { |
| | | return lhs.type == rhs.type && lhs.value == rhs.value && lhs.start_pos == rhs.start_pos && |
| | | lhs.end_pos == rhs.end_pos; |
| | | } |
| | | }; // namespace Lexer::Tokens |
| | | #endif // TOKEN_HPP |
| New file |
| | |
| | | #ifndef TOKEN_TYPE_HPP |
| | | #define TOKEN_TYPE_HPP |
| | | |
| | | #include <cstdint> |
| | | #include <string> |
| | | |
| | | namespace Lexer::Tokens { |
| | | |
| | | enum class Type : std::uint8_t { |
| | | IDENTIFIER, // Pl. változónév: myVar |
| | | VARIABLE_IDENTIFIER, // Pl. $ |
| | | KEYWORD, // Pl. if, else, while |
| | | NUMBER, // Pl. 123, 42 |
| | | STRING_LITERAL, // Pl. "hello world" |
| | | OPERATOR_RELATIONAL, // Pl. +, -, *, /, =, ==, <, > |
| | | OPERATOR_LOGICAL, // Pl. &&, || |
| | | OPERATOR_ASSIGNMENT, // Pl. +=, -=, *=, /= |
| | | OPERATOR_INCREMENT, // Pl. ++, -- |
| | | OPERATOR_ARITHMETIC, // Pl. +, -, *, / |
| | | PUNCTUATION, // Pl. (, ), {, }, ;, , |
| | | COMMENT, // Általában kihagyjuk |
| | | END_OF_FILE, // A string végét jelzi |
| | | KEYWORD_STRING, |
| | | KEYWORD_INT, |
| | | KEYWORD_DOUBLE, |
| | | KEYWORD_FLOAT, |
| | | KEYWORD_BOOLEAN, |
| | | KEYWORD_FUNCTION, |
| | | KEYWORD_RETURN, |
| | | KEYWORD_NULL, |
| | | UNKNOWN // Ismeretlen karakter/szekvencia |
| | | }; |
| | | |
| | | inline std::string TypeToString(Lexer::Tokens::Type type) { |
| | | switch (type) { |
| | | case Lexer::Tokens::Type::IDENTIFIER: |
| | | return "IDENTIFIER"; |
| | | case Lexer::Tokens::Type::VARIABLE_IDENTIFIER: |
| | | return "VARIABLE_IDENTIFIER"; |
| | | case Lexer::Tokens::Type::KEYWORD: |
| | | return "KEYWORD"; |
| | | case Lexer::Tokens::Type::NUMBER: |
| | | return "NUMBER"; |
| | | case Lexer::Tokens::Type::STRING_LITERAL: |
| | | return "STRING_LITERAL"; |
| | | case Lexer::Tokens::Type::OPERATOR_RELATIONAL: |
| | | return "OPERATOR_RELATIONAL"; |
| | | case Lexer::Tokens::Type::OPERATOR_LOGICAL: |
| | | return "OPERATOR_LOGICAL"; |
| | | case Lexer::Tokens::Type::OPERATOR_ASSIGNMENT: |
| | | return "OPERATOR_ASSIGNMENT"; |
| | | case Lexer::Tokens::Type::OPERATOR_INCREMENT: |
| | | return "OPERATOR_INCREMENT"; |
| | | case Lexer::Tokens::Type::OPERATOR_ARITHMETIC: |
| | | return "OPERATOR_ARITHMETIC"; |
| | | case Lexer::Tokens::Type::PUNCTUATION: |
| | | return "PUNCTUATION"; |
| | | case Lexer::Tokens::Type::COMMENT: |
| | | return "COMMENT"; |
| | | case Lexer::Tokens::Type::END_OF_FILE: |
| | | return "END_OF_FILE"; |
| | | case Lexer::Tokens::Type::KEYWORD_STRING: |
| | | return "KEYWORD_STRING"; |
| | | case Lexer::Tokens::Type::KEYWORD_INT: |
| | | return "KEYWORD_INT"; |
| | | case Lexer::Tokens::Type::KEYWORD_DOUBLE: |
| | | return "KEYWORD_DOUBLE"; |
| | | case Lexer::Tokens::Type::KEYWORD_FLOAT: |
| | | return "KEYWORD_FLOAT"; |
| | | case Lexer::Tokens::Type::KEYWORD_BOOLEAN: |
| | | return "KEYWORD_BOOLEAN"; |
| | | case Lexer::Tokens::Type::KEYWORD_FUNCTION: |
| | | return "KEYWORD_FUNCTION"; |
| | | case Lexer::Tokens::Type::KEYWORD_RETURN: |
| | | return "KEYWORD_RETURN"; |
| | | case Lexer::Tokens::Type::KEYWORD_NULL: |
| | | return "KEYWORD_NULL"; |
| | | case Lexer::Tokens::Type::UNKNOWN: |
| | | return "UNKNOWN"; |
| | | default: |
| | | return "INVALID_TYPE"; |
| | | } |
| | | } |
| | | |
| | | }; // namespace Lexer::Tokens |
| | | |
| | | #endif // TOKEN_TYPE_HPP |
| New file |
| | |
| | | #include "Parser/Parser.hpp" |
| | | |
| | | // Más szükséges include-ok, ha kellenek |
| | | namespace Parser { |
| | | |
| | | const std::unordered_map<std::string, Lexer::Tokens::Type> Parser::keywords = { |
| | | { "if", Lexer::Tokens::Type::KEYWORD }, |
| | | { "else", Lexer::Tokens::Type::KEYWORD }, |
| | | { "while", Lexer::Tokens::Type::KEYWORD }, |
| | | { "for", Lexer::Tokens::Type::KEYWORD }, |
| | | { "return", Lexer::Tokens::Type::KEYWORD_RETURN }, |
| | | { "function", Lexer::Tokens::Type::KEYWORD_FUNCTION }, |
| | | // Régebbiek: |
| | | { "const", Lexer::Tokens::Type::KEYWORD }, |
| | | { "true", Lexer::Tokens::Type::KEYWORD }, |
| | | { "false", Lexer::Tokens::Type::KEYWORD }, |
| | | // változó típusok |
| | | { "null", Lexer::Tokens::Type::KEYWORD_NULL }, |
| | | { "int", Lexer::Tokens::Type::KEYWORD_INT }, |
| | | { "double", Lexer::Tokens::Type::KEYWORD_DOUBLE }, |
| | | { "float", Lexer::Tokens::Type::KEYWORD_FLOAT }, |
| | | { "string", Lexer::Tokens::Type::KEYWORD_STRING }, |
| | | { "boolean", Lexer::Tokens::Type::KEYWORD_BOOLEAN }, |
| | | { "bool", Lexer::Tokens::Type::KEYWORD_BOOLEAN }, |
| | | // ... egyéb kulcsszavak ... |
| | | }; |
| | | |
| | | const std::unordered_map<Lexer::Tokens::Type, Symbols::Variables::Type> Parser::variable_types = { |
| | | { Lexer::Tokens::Type::KEYWORD_INT, Symbols::Variables::Type::INTEGER }, |
| | | { Lexer::Tokens::Type::KEYWORD_DOUBLE, Symbols::Variables::Type::DOUBLE }, |
| | | { Lexer::Tokens::Type::KEYWORD_FLOAT, Symbols::Variables::Type::FLOAT }, |
| | | { Lexer::Tokens::Type::KEYWORD_STRING, Symbols::Variables::Type::STRING }, |
| | | { Lexer::Tokens::Type::KEYWORD_NULL, Symbols::Variables::Type::NULL_TYPE }, |
| | | { Lexer::Tokens::Type::KEYWORD_BOOLEAN, Symbols::Variables::Type::BOOLEAN }, |
| | | }; |
| | | |
| | | } // namespace Parser |
| New file |
| | |
| | | #ifndef PARSER_HPP |
| | | #define PARSER_HPP |
| | | |
| | | #include <algorithm> |
| | | #include <memory> |
| | | #include <sstream> // Hibaüzenetekhez |
| | | #include <stdexcept> |
| | | #include <string> |
| | | #include <vector> |
| | | |
| | | // Szükséges header-ök a Lexer és Symbol komponensekből |
| | | #include "Lexer/Token.hpp" |
| | | #include "Lexer/TokenType.hpp" // Enum és TypeToString |
| | | #include "Symbols/SymbolContainer.hpp" |
| | | #include "Symbols/SymbolFactory.hpp" |
| | | #include "Symbols/Value.hpp" // Symbols::Value miatt |
| | | |
| | | namespace Parser { |
| | | |
| | | class SyntaxError : public std::runtime_error { |
| | | public: |
| | | SyntaxError(const std::string & message, const int line, const int col) : |
| | | std::runtime_error(message + " at line " + std::to_string(line) + ", column " + std::to_string(col)) {} |
| | | |
| | | SyntaxError(const std::string & message, const Lexer::Tokens::Token & token) : |
| | | SyntaxError( |
| | | message + " (found token: '" + token.value + "' type: " + Lexer::Tokens::TypeToString(token.type) + ")", |
| | | token.line_number, token.column_number) {} |
| | | }; |
| | | |
| | | class Parser { |
| | | public: |
| | | Parser() {} |
| | | |
| | | void parseProgram(const std::vector<Lexer::Tokens::Token> & tokens, std::string_view input_string) { |
| | | tokens_ = tokens; |
| | | input_str_view_ = input_string; |
| | | current_token_index_ = 0; |
| | | symbol_container_ = std::make_unique<Symbols::SymbolContainer>(); |
| | | try { |
| | | while (!isAtEnd() && currentToken().type != Lexer::Tokens::Type::END_OF_FILE) { |
| | | parseStatement(); |
| | | } |
| | | if (!isAtEnd() && currentToken().type != Lexer::Tokens::Type::END_OF_FILE) { |
| | | reportError("Unexpected tokens after program end"); |
| | | } |
| | | } catch (const SyntaxError & e) { |
| | | std::cerr << "Syntax Error: " << e.what() << '\n'; |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "Error during parsing: " << e.what() << '\n'; |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | const std::shared_ptr<Symbols::SymbolContainer> & getSymbolContainer() const { |
| | | if (!symbol_container_) { |
| | | throw std::runtime_error("Symbol container is not initialized."); |
| | | } |
| | | return symbol_container_; |
| | | } |
| | | |
| | | static const std::unordered_map<std::string, Lexer::Tokens::Type> keywords; |
| | | static const std::unordered_map<Lexer::Tokens::Type, Symbols::Variables::Type> variable_types; |
| | | |
| | | private: |
| | | std::vector<Lexer::Tokens::Token> tokens_; |
| | | std::string_view input_str_view_; |
| | | size_t current_token_index_; |
| | | std::shared_ptr<Symbols::SymbolContainer> symbol_container_; |
| | | |
| | | // Token Stream Kezelő és Hibakezelő segédfüggvények (változatlanok) |
| | | const Lexer::Tokens::Token & currentToken() const { |
| | | if (isAtEnd()) { |
| | | // Technikailag itt már nem kellene lennünk, ha a parseProgram ciklus jól van megírva |
| | | // De biztonsági ellenőrzésként jó lehet |
| | | if (!tokens_.empty() && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE) { |
| | | return tokens_.back(); // Visszaadjuk az EOF tokent |
| | | } |
| | | throw std::runtime_error("Unexpected end of token stream reached."); |
| | | } |
| | | return tokens_[current_token_index_]; |
| | | } |
| | | |
| | | // Előre néz a token stream-ben |
| | | const Lexer::Tokens::Token & peekToken(size_t offset = 1) const { |
| | | if (current_token_index_ + offset >= tokens_.size()) { |
| | | // EOF vagy azon túl vagyunk, adjuk vissza az utolsó tokent (ami EOF kell legyen) |
| | | if (!tokens_.empty()) { |
| | | return tokens_.back(); |
| | | } |
| | | throw std::runtime_error("Cannot peek beyond end of token stream."); |
| | | } |
| | | return tokens_[current_token_index_ + offset]; |
| | | } |
| | | |
| | | // Elfogyasztja (lépteti az indexet) az aktuális tokent és visszaadja azt |
| | | Lexer::Tokens::Token consumeToken() { |
| | | if (isAtEnd()) { |
| | | throw std::runtime_error("Cannot consume token at end of stream."); |
| | | } |
| | | return tokens_[current_token_index_++]; |
| | | } |
| | | |
| | | // Ellenőrzi, hogy az aktuális token típusa megegyezik-e a várttal. |
| | | // Ha igen, elfogyasztja és true-t ad vissza. Ha nem, false-t ad vissza. |
| | | bool match(Lexer::Tokens::Type expected_type) { |
| | | if (isAtEnd()) { |
| | | return false; |
| | | } |
| | | if (currentToken().type == expected_type) { |
| | | consumeToken(); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // Ellenőrzi, hogy az aktuális token típusa és értéke megegyezik-e a várttal. |
| | | // Csak OPERATOR és PUNCTUATION esetén érdemes használni az érték ellenőrzést. |
| | | bool match(Lexer::Tokens::Type expected_type, const std::string & expected_value) { |
| | | if (isAtEnd()) { |
| | | return false; |
| | | } |
| | | const auto & token = currentToken(); |
| | | if (token.type == expected_type && token.value == expected_value) { |
| | | consumeToken(); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type) { |
| | | if (isAtEnd()) { |
| | | reportError("Unexpected end of file, expected token type: " + Lexer::Tokens::TypeToString(expected_type)); |
| | | } |
| | | const auto & token = currentToken(); |
| | | if (token.type == expected_type) { |
| | | return consumeToken(); |
| | | } |
| | | reportError("Expected token type " + Lexer::Tokens::TypeToString(expected_type)); |
| | | // A reportError dob, ez a return sosem fut le, de a fordító kedvéért kellhet: |
| | | return token; // Vagy dobjon a reportError |
| | | } |
| | | |
| | | // Mint az expect, de az értékét is ellenőrzi. |
| | | Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type, const std::string & expected_value) { |
| | | if (isAtEnd()) { |
| | | reportError("Unexpected end of file, expected token: " + Lexer::Tokens::TypeToString(expected_type) + |
| | | " with value '" + expected_value + "'"); |
| | | } |
| | | const auto & token = currentToken(); |
| | | if (token.type == expected_type && token.value == expected_value) { |
| | | return consumeToken(); |
| | | } |
| | | reportError("Expected token " + Lexer::Tokens::TypeToString(expected_type) + " with value '" + expected_value + |
| | | "'"); |
| | | return token; // reportError dob |
| | | } |
| | | |
| | | // Ellenőrzi, hogy a releváns tokenek végére értünk-e (az EOF előtti utolsó tokenen vagyunk-e) |
| | | bool isAtEnd() const { |
| | | // Akkor vagyunk a végén, ha az index a tokenek méretével egyenlő, |
| | | // vagy ha már csak az EOF token van hátra (ha az a lista utolsó eleme). |
| | | return current_token_index_ >= tokens_.size() || |
| | | (current_token_index_ == tokens_.size() - 1 && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE); |
| | | } |
| | | |
| | | // --- Hibakezelés --- |
| | | // Hiba jelentése és kivétel dobása |
| | | [[noreturn]] void reportError(const std::string & message) { |
| | | // Használjuk az aktuális token pozícióját, ha még nem értünk a végére |
| | | if (current_token_index_ < tokens_.size()) { |
| | | throw SyntaxError(message, tokens_[current_token_index_]); |
| | | } // Ha már a végén vagyunk, az utolsó ismert pozíciót használjuk |
| | | int line = tokens_.empty() ? 0 : tokens_.back().line_number; |
| | | int col = tokens_.empty() ? 0 : tokens_.back().column_number; |
| | | throw SyntaxError(message, line, col); |
| | | } |
| | | |
| | | // --- Elemzési Módszerek (Moduláris részek) --- |
| | | |
| | | // parseStatement (változatlan) |
| | | void parseStatement() { |
| | | const auto & token_type = currentToken().type; |
| | | |
| | | if (token_type == Lexer::Tokens::Type::KEYWORD_FUNCTION) { |
| | | parseFunctionDefinition(); |
| | | return; |
| | | } |
| | | |
| | | for (const auto & _type : Parser::Parser::variable_types) { |
| | | if (token_type == _type.first) { |
| | | parseVariableDefinition(); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | reportError("Unexpected token at beginning of statement"); |
| | | } |
| | | |
| | | // parseVariableDefinition (SymbolFactory használata már korrekt volt) |
| | | void parseVariableDefinition() { |
| | | Symbols::Variables::Type var_type_enum = parseType(); |
| | | // A típus stringjének tárolása csak a debug kiíráshoz kell |
| | | //std::string var_type_str = tokens_[current_token_index_ - 1].value; |
| | | std::cout << "var_name: " << currentToken().lexeme << std::endl; |
| | | Lexer::Tokens::Token id_token = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER); |
| | | std::string var_name = id_token.value; |
| | | // Levágjuk a '$' jelet, ha a lexer mégis benne hagyta |
| | | if (!var_name.empty() && var_name[0] == '$') { |
| | | var_name = var_name.substr(1); |
| | | } |
| | | |
| | | expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "="); |
| | | Symbols::Value initial_value = parseValue(var_type_enum); |
| | | expect(Lexer::Tokens::Type::PUNCTUATION, ";"); |
| | | |
| | | std::string context = "global"; // Globális változó |
| | | // SymbolFactory használata a létrehozáshoz |
| | | auto variable_symbol = Symbols::SymbolFactory::createVariable(var_name, initial_value, context, var_type_enum); |
| | | |
| | | // Ellenőrzés és definíció az *aktuális* scope-ban (ami itt a globális) |
| | | if (symbol_container_->exists("variables", var_name)) { |
| | | reportError("Variable '" + var_name + "' already defined in this scope"); |
| | | } |
| | | symbol_container_->define("variables", variable_symbol); |
| | | |
| | | // Debugging kiírás (változatlan) |
| | | // std::cout << "Parsed variable: " << var_type_str << " " << id_token.value << " = ... ;\n"; |
| | | } |
| | | |
| | | // *** MÓDOSÍTOTT parseFunctionDefinition *** |
| | | void parseFunctionDefinition() { |
| | | expect(Lexer::Tokens::Type::KEYWORD_FUNCTION); |
| | | Lexer::Tokens::Token id_token = expect(Lexer::Tokens::Type::IDENTIFIER); |
| | | std::string func_name = id_token.value; |
| | | Symbols::Variables::Type func_return_type = Symbols::Variables::Type::NULL_TYPE; |
| | | expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "="); |
| | | expect(Lexer::Tokens::Type::PUNCTUATION, "("); |
| | | |
| | | Symbols::FunctionParameterInfo param_infos; |
| | | |
| | | if (currentToken().type != Lexer::Tokens::Type::PUNCTUATION || currentToken().value != ")") { |
| | | while (true) { |
| | | // Paraméter típusa |
| | | //size_t type_token_index = current_token_index_; // Elmentjük a típus token indexét |
| | | Symbols::Variables::Type param_type = parseType(); // Ez elfogyasztja a type tokent |
| | | |
| | | // Paraméter név ($variable) |
| | | Lexer::Tokens::Token param_id_token = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER); |
| | | std::string param_name = param_id_token.value; |
| | | if (!param_name.empty() && param_name[0] == '$') { // '$' eltávolítása |
| | | param_name = param_name.substr(1); |
| | | } |
| | | |
| | | param_infos.push_back({ param_name, param_type }); |
| | | |
| | | // Vessző vagy zárójel következik? |
| | | if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) { |
| | | continue; |
| | | } |
| | | if (currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ")") { |
| | | break; // Lista vége |
| | | } |
| | | reportError("Expected ',' or ')' in parameter list"); |
| | | } |
| | | } |
| | | // Most a ')' következik |
| | | expect(Lexer::Tokens::Type::PUNCTUATION, ")"); |
| | | |
| | | // check if we have a option return type: function name() type { ... } |
| | | for (const auto & _type : Parser::variable_types) { |
| | | if (match(_type.first)) { |
| | | func_return_type = _type.second; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | auto function_symbol = |
| | | Symbols::SymbolFactory::createFunction(func_name, "global", param_infos, "", func_return_type); |
| | | if (symbol_container_->exists("functions", func_name)) { |
| | | reportError("Function '" + func_name + "' already defined in this scope"); |
| | | } |
| | | |
| | | Lexer::Tokens::Token opening_brace = expect(Lexer::Tokens::Type::PUNCTUATION, "{"); |
| | | |
| | | symbol_container_->define("functions", function_symbol); |
| | | |
| | | // only parse the body if we checked out if not exists the function and created the symbol |
| | | Symbols::SymbolContainer func_symbols = |
| | | parseFunctionBody(opening_brace, func_return_type != Symbols::Variables::Type::NULL_TYPE); |
| | | |
| | | // create new container for the function |
| | | |
| | | std::cout << "Defined function symbol: " << func_name << " in global scope.\n"; |
| | | |
| | | // 3. Új scope nyitása a függvény paraméterei (és később lokális változói) számára |
| | | symbol_container_->enterScope(); |
| | | //std::cout << "Entered scope for function: " << func_name << "\n"; |
| | | |
| | | // 4. Paraméterek definiálása mint változók az *új* (függvény) scope-ban |
| | | const std::string & param_context = func_name; // Paraméter kontextusa a függvény neve |
| | | for (const auto & p_info : param_infos) { |
| | | auto param_symbol = Symbols::SymbolFactory::createVariable(p_info.name, // Név '$' nélkül |
| | | Symbols::Value(), // Alapértelmezett/üres érték |
| | | param_context, // Kontextus |
| | | p_info.type // Típus |
| | | ); |
| | | |
| | | if (symbol_container_->exists("variables", p_info.name)) { |
| | | reportError("Parameter name '" + p_info.name + "' conflicts with another symbol in function '" + |
| | | func_name + "'"); |
| | | } |
| | | symbol_container_->define("variables", param_symbol); |
| | | } |
| | | |
| | | // 5. Függvény scope elhagyása |
| | | symbol_container_->leaveScope(); |
| | | //std::cout << "Left scope for function: " << func_name << "\n"; |
| | | |
| | | // Debugging kiírás (változatlan) |
| | | // std::cout << "Parsed function: " << func_name << " (...) { ... } scope handled.\n"; |
| | | } |
| | | |
| | | // --- Elemzési Segédfüggvények --- |
| | | |
| | | // type : KEYWORD_STRING | KEYWORD_INT | KEYWORD_DOUBLE |
| | | // Visszaadja a megfelelő Symbols::Variables::Type enum értéket és elfogyasztja a tokent. |
| | | Symbols::Variables::Type parseType() { |
| | | const auto & token = currentToken(); |
| | | for (const auto & _type : Parser::variable_types) { |
| | | if (token.type == _type.first) { |
| | | consumeToken(); |
| | | return _type.second; |
| | | } |
| | | } |
| | | |
| | | reportError("Expected type keyword (string, int, double, float)"); |
| | | } |
| | | |
| | | // value : STRING_LITERAL | NUMBER |
| | | // Feldolgozza az értéket a várt típus alapján. |
| | | Symbols::Value parseValue(Symbols::Variables::Type expected_var_type) { |
| | | Lexer::Tokens::Token token = currentToken(); |
| | | |
| | | /// find if this is a function call |
| | | if (token.type == Lexer::Tokens::Type::IDENTIFIER && peekToken().lexeme == "(") { |
| | | for (const auto & symbol_ptr : this->symbol_container_->listNamespace("functions")) { |
| | | if (auto func_symbol = std::dynamic_pointer_cast<Symbols::FunctionSymbol>(symbol_ptr)) { |
| | | // TODO: A függvény hívását kellene feldolgozni, a func_symbol-ból kellene lekérni a paramétereket, func_symbol->plainBody() tartalmazza a függvény törzsét |
| | | // A függvény hívásának feldolgozása |
| | | std::cout << "Function call: " << token.value << "\n"; |
| | | while (consumeToken().lexeme != ")") { |
| | | // Feltételezzük, hogy a függvény hívását a lexer már feldolgozta |
| | | } |
| | | return Symbols::Value(""); // TODO: Implementálni a függvény hívását |
| | | } |
| | | } |
| | | } |
| | | |
| | | bool is_negative = false; |
| | | std::cout << "Peek: " << peekToken().lexeme << "\n"; |
| | | if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC && peekToken().type == Lexer::Tokens::Type::NUMBER) { |
| | | is_negative = true; |
| | | token = peekToken(); |
| | | consumeToken(); |
| | | } |
| | | |
| | | if (expected_var_type == Symbols::Variables::Type::STRING) { |
| | | if (token.type == Lexer::Tokens::Type::STRING_LITERAL) { |
| | | consumeToken(); |
| | | return Symbols::Value(token.value); // A lexer value-ja már a feldolgozott string |
| | | } |
| | | reportError("Expected string literal value"); |
| | | } else if (expected_var_type == Symbols::Variables::Type::INTEGER) { |
| | | if (token.type == Lexer::Tokens::Type::NUMBER) { |
| | | // Konvertálás int-re, hibakezeléssel |
| | | try { |
| | | // TODO: Ellenőrizni, hogy a szám valóban egész-e (nincs benne '.') |
| | | // Most egyszerűen std::stoi-t használunk. |
| | | int int_value = std::stoi(token.value); |
| | | if (is_negative) { |
| | | int_value = -int_value; |
| | | } |
| | | consumeToken(); |
| | | return Symbols::Value(int_value); |
| | | } catch (const std::invalid_argument & e) { |
| | | reportError("Invalid integer literal: " + token.value); |
| | | } catch (const std::out_of_range & e) { |
| | | reportError("Integer literal out of range: " + token.value); |
| | | } |
| | | } |
| | | reportError("Expected integer literal value"); |
| | | } else if (expected_var_type == Symbols::Variables::Type::DOUBLE) { |
| | | if (token.type == Lexer::Tokens::Type::NUMBER) { |
| | | // Konvertálás double-re, hibakezeléssel |
| | | try { |
| | | double double_value = std::stod(token.value); |
| | | if (is_negative) { |
| | | double_value = -double_value; |
| | | } |
| | | consumeToken(); |
| | | return Symbols::Value(double_value); |
| | | } catch (const std::invalid_argument & e) { |
| | | reportError("Invalid double literal: " + token.value); |
| | | } catch (const std::out_of_range & e) { |
| | | reportError("Double literal out of range: " + token.value); |
| | | } |
| | | } |
| | | reportError("Expected numeric literal value for double"); |
| | | } else if (expected_var_type == Symbols::Variables::Type::FLOAT) { |
| | | if (token.type == Lexer::Tokens::Type::NUMBER) { |
| | | // Konvertálás double-re, hibakezeléssel |
| | | try { |
| | | |
| | | float float_value = std::atof(token.value.data()); |
| | | if (is_negative) { |
| | | float_value = -float_value; |
| | | } |
| | | consumeToken(); |
| | | return Symbols::Value(float_value); |
| | | } catch (const std::invalid_argument & e) { |
| | | reportError("Invalid float literal: " + token.value); |
| | | } catch (const std::out_of_range & e) { |
| | | reportError("Float literal out of range: " + token.value); |
| | | } |
| | | } |
| | | reportError("Expected numeric literal value for double"); |
| | | } else if (expected_var_type == Symbols::Variables::Type::BOOLEAN) { |
| | | if (token.type == Lexer::Tokens::Type::KEYWORD) { |
| | | consumeToken(); |
| | | return Symbols::Value(token.value == "true"); // A lexer value-ja már a feldolgozott string |
| | | } |
| | | reportError("Expected boolean literal value"); |
| | | } else { |
| | | // Más típusok (pl. boolean) itt kezelendők, ha lennének |
| | | reportError("Unsupported variable type encountered during value parsing"); |
| | | } |
| | | // Should not reach here due to reportError throwing |
| | | return Symbols::Value(); // Default return to satisfy compiler |
| | | } |
| | | |
| | | Symbols::SymbolContainer parseFunctionBody(const Lexer::Tokens::Token & opening_brace, |
| | | bool return_required = false) { |
| | | size_t braceDepth = 0; |
| | | int tokenIndex = current_token_index_; |
| | | Lexer::Tokens::Token currentToken_; |
| | | Lexer::Tokens::Token closing_brace; |
| | | |
| | | while (tokenIndex < tokens_.size()) { |
| | | currentToken_ = peekToken(); |
| | | |
| | | if (currentToken_.type == Lexer::Tokens::Type::PUNCTUATION) { |
| | | if (currentToken_.value == "{") { |
| | | ++braceDepth; |
| | | } else if (currentToken_.value == "}") { |
| | | if (braceDepth == 0) { |
| | | closing_brace = currentToken_; |
| | | break; |
| | | } |
| | | --braceDepth; |
| | | } |
| | | } |
| | | } |
| | | if (braceDepth != 0) { |
| | | reportError("Unmatched braces in function body"); |
| | | } |
| | | std::vector<Lexer::Tokens::Token> filtered_tokens; |
| | | auto startIt = std::find(tokens_.begin(), tokens_.end(), opening_brace); |
| | | auto endIt = std::find(tokens_.begin(), tokens_.end(), closing_brace); |
| | | |
| | | // Ellenőrzés: mindkét token megtalálva és start_token megelőzi az end_token-t |
| | | if (startIt != tokens_.end() && endIt != tokens_.end() && startIt < endIt) { |
| | | filtered_tokens = std::vector<Lexer::Tokens::Token>(startIt + 1, endIt); |
| | | } |
| | | std::string_view input_string = input_str_view_.substr(opening_brace.end_pos, closing_brace.end_pos); |
| | | auto parser = Parser(); |
| | | parser.parseProgram(filtered_tokens, input_string); |
| | | |
| | | return *parser.getSymbolContainer(); |
| | | } |
| | | |
| | | std::string parseFunctionBodyOld(size_t body_start_pos, bool return_required = false) { |
| | | std::stringstream body_ss; |
| | | int brace_level = 1; |
| | | size_t last_token_end_pos = body_start_pos; |
| | | bool has_return = false; |
| | | |
| | | while (true) { |
| | | if (isAtEnd()) { |
| | | reportError("Unexpected end of file inside function body (missing '}')"); |
| | | } |
| | | const auto & token = currentToken(); |
| | | |
| | | // Whitespace visszaállítása (ha volt) az 'input_str_view_' alapján |
| | | if (token.start_pos > last_token_end_pos) { |
| | | if (last_token_end_pos < input_str_view_.length() && token.start_pos <= input_str_view_.length()) { |
| | | body_ss << input_str_view_.substr(last_token_end_pos, token.start_pos - last_token_end_pos); |
| | | } else { |
| | | reportError("Invalid position range in body reconstruction"); |
| | | } |
| | | } |
| | | |
| | | if (token.type == Lexer::Tokens::Type::KEYWORD_RETURN) { |
| | | has_return = true; |
| | | } |
| | | |
| | | if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "{") { |
| | | brace_level++; |
| | | body_ss << token.lexeme; |
| | | } else if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "}") { |
| | | brace_level--; |
| | | if (brace_level == 0) { |
| | | consumeToken(); // Záró '}' elfogyasztása |
| | | break; |
| | | } |
| | | body_ss << token.lexeme; |
| | | } else { |
| | | body_ss << token.lexeme; |
| | | } |
| | | |
| | | last_token_end_pos = token.end_pos; |
| | | consumeToken(); |
| | | } |
| | | |
| | | if (return_required && !has_return) { |
| | | return ""; |
| | | } |
| | | |
| | | return body_ss.str(); |
| | | } |
| | | |
| | | }; // class Parser |
| | | |
| | | } // namespace Parser |
| | | |
| | | #endif // PARSER_HPP |
| File was renamed from include/BaseSymbol.hpp |
| | |
| | | #define BASE_SYMBOL_HPP |
| | | |
| | | #include <string> |
| | | #include <variant> |
| | | |
| | | #include "SymbolKind.hpp" |
| | | #include "Value.hpp" |
| | | |
| | | namespace Symbols { |
| | | |
| | | using Value = std::variant<int, double, std::string, bool>; |
| | | |
| | | class Symbol { |
| | | protected: |
| New file |
| | |
| | | // FunctionSymbol.hpp |
| | | #ifndef FUNCTION_SYMBOL_HPP |
| | | #define FUNCTION_SYMBOL_HPP |
| | | |
| | | #include <vector> |
| | | |
| | | #include "BaseSymbol.hpp" |
| | | #include "Lexer/Token.hpp" |
| | | #include "Symbols/ParameterContainer.hpp" |
| | | #include "Symbols/VariableTypes.hpp" |
| | | |
| | | namespace Symbols { |
| | | |
| | | class FunctionSymbol : public Symbol { |
| | | // store the variables name and type |
| | | FunctionParameterInfo parameters_; |
| | | Symbols::Variables::Type returnType_; |
| | | std::string plainBody_; |
| | | std::vector<Lexer::Tokens::Token> tokens_; |
| | | |
| | | public: |
| | | FunctionSymbol(const std::string & name, const std::string & context, const FunctionParameterInfo & parameters, |
| | | const std::string & plainbody = "", |
| | | Symbols::Variables::Type returnType = Symbols::Variables::Type::NULL_TYPE) : |
| | | Symbol(name, {}, context, Symbols::Kind::Function), |
| | | parameters_(parameters), |
| | | plainBody_(plainbody), |
| | | returnType_(returnType) {} |
| | | |
| | | Symbols::Kind kind() const override { return Symbols::Kind::Function; } |
| | | |
| | | Symbols::Variables::Type returnType() const { return returnType_; } |
| | | |
| | | const FunctionParameterInfo & parameters() const { return parameters_; } |
| | | |
| | | const std::string & plainBody() const { return plainBody_; } |
| | | }; |
| | | |
| | | } // namespace Symbols |
| | | |
| | | #endif |
| New file |
| | |
| | | #ifndef SYMBOLS_PARAMETER_CONTAINER_HPP |
| | | #define SYMBOLS_PARAMETER_CONTAINER_HPP |
| | | |
| | | #include <string> |
| | | #include <vector> |
| | | |
| | | #include "Symbols/VariableTypes.hpp" |
| | | |
| | | namespace Symbols { |
| | | struct functionParameterType { |
| | | std::string name; |
| | | Symbols::Variables::Type type; |
| | | }; |
| | | |
| | | using FunctionParameterInfo = std::vector<Symbols::functionParameterType>; |
| | | } // namespace Symbols |
| | | #endif |
| File was renamed from include/SymbolFactory.hpp |
| | |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createFunction(const std::string & name, const std::string & context, |
| | | const Symbols::ValueContainer & parameters = {}) { |
| | | const Symbols::FunctionParameterInfo & parameters = {}) { |
| | | return std::make_shared<FunctionSymbol>(name, context, parameters); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createFunction(const std::string & name, const std::string & context, |
| | | const Symbols::ValueContainer & parameters, |
| | | const Symbols::FunctionParameterInfo & parameters, |
| | | const std::string & plainBody) { |
| | | return std::make_shared<FunctionSymbol>(name, context, parameters, plainBody); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createFunction(const std::string & name, const std::string & context, |
| | | const Symbols::FunctionParameterInfo & parameters, |
| | | const std::string & plainBody, Symbols::Variables::Type returnType) { |
| | | return std::make_shared<FunctionSymbol>(name, context, parameters, plainBody, returnType); |
| | | } |
| | | |
| | | // Overloadok |
| | | static std::shared_ptr<Symbol> createVariable(const std::string & name, int value, const std::string & context) { |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::VT_INT); |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::INTEGER); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createVariable(const std::string & name, double value, const std::string & context) { |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::VT_DOUBLE); |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::DOUBLE); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createVariable(const std::string & name, float value, const std::string & context) { |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::FLOAT); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createVariable(const std::string & name, const std::string & value, |
| | | const std::string & context) { |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::VT_STRING); |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::STRING); |
| | | } |
| | | |
| | | static std::shared_ptr<Symbol> createVariable(const std::string & name, bool value, const std::string & context) { |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::VT_BOOLEAN); |
| | | return createVariable(name, Symbols::Value(value), context, Symbols::Variables::Type::BOOLEAN); |
| | | } |
| | | }; |
| | | |
| File was renamed from include/SymbolTable.hpp |
| | |
| | | #define SYMBOL_TABLE_HPP |
| | | |
| | | #include <memory> |
| | | #include <optional> |
| | | #include <vector> |
| | | |
| | | #include "SymbolTypes.hpp" |
| New file |
| | |
| | | #ifndef SYMBOL_VALUE_HPP |
| | | #define SYMBOL_VALUE_HPP |
| | | |
| | | #include <iostream> |
| | | #include <stdexcept> |
| | | #include <string> |
| | | #include <variant> |
| | | |
| | | namespace Symbols { |
| | | |
| | | class Value { |
| | | public: |
| | | using Variant = std::variant<int, double, float, std::string, bool>; |
| | | |
| | | Value() = default; |
| | | |
| | | Value(int v) : value_(v) {} |
| | | |
| | | Value(double v) : value_(v) {} |
| | | |
| | | Value(float v) : value_(v) {} |
| | | |
| | | Value(const std::string & v) : value_(v) {} |
| | | |
| | | Value(const char * v) : value_(std::string(v)) {} |
| | | |
| | | Value(bool v) : value_(v) {} |
| | | |
| | | const Variant & get() const { return value_; } |
| | | |
| | | Variant & get() { return value_; } |
| | | |
| | | template <typename T> T get() const { return std::get<T>(value_); } |
| | | |
| | | // operator+ |
| | | friend Value operator+(const Value & lhs, const Value & rhs) { |
| | | return std::visit( |
| | | [](auto && a, auto && b) -> Value { |
| | | using A = std::decay_t<decltype(a)>; |
| | | using B = std::decay_t<decltype(b)>; |
| | | if constexpr ((std::is_arithmetic_v<A> && std::is_arithmetic_v<B>) ) { |
| | | return Value{ a + b }; |
| | | } else { |
| | | throw std::runtime_error("Invalid types for operator+"); |
| | | } |
| | | }, |
| | | lhs.value_, rhs.value_); |
| | | } |
| | | |
| | | // operator== |
| | | friend bool operator==(const Value & lhs, const Value & rhs) { return lhs.value_ == rhs.value_; } |
| | | |
| | | // to_string helper (needed for string + int, etc.) |
| | | static std::string to_string(const Variant & v) { |
| | | return std::visit( |
| | | [](auto && val) -> std::string { |
| | | using T = std::decay_t<decltype(val)>; |
| | | if constexpr (std::is_same_v<T, bool>) { |
| | | return val ? "true" : "false"; |
| | | } else if constexpr (std::is_same_v<T, std::string>) { |
| | | return val; |
| | | } else { |
| | | return std::to_string(val); |
| | | } |
| | | }, |
| | | v); |
| | | } |
| | | |
| | | static std::string to_string(const Value & val) { return to_string(val.value_); } |
| | | |
| | | private: |
| | | Variant value_; |
| | | }; |
| | | |
| | | } // namespace Symbols |
| | | |
| | | #endif // SYMBOL_VALUE_HPP |
| New file |
| | |
| | | #ifndef SYMBOLS_VALUE_CONTAINER_HPP |
| | | #define SYMBOLS_VALUE_CONTAINER_HPP |
| | | |
| | | #include <vector> |
| | | |
| | | #include "Symbols/Value.hpp" |
| | | |
| | | namespace Symbols { |
| | | using ValueContainer = std::vector<Symbols::Value>; |
| | | }; // namespace Symbols |
| | | |
| | | #endif // SYMBOLS_VALUE_CONTAINER_HPP |
| New file |
| | |
| | | // VariableSymbol.hpp |
| | | #ifndef VARIABLE_SYMBOL_HPP |
| | | #define VARIABLE_SYMBOL_HPP |
| | | |
| | | #include "BaseSymbol.hpp" |
| | | #include "Symbols/VariableTypes.hpp" |
| | | |
| | | namespace Symbols { |
| | | |
| | | class VariableSymbol : public Symbol { |
| | | protected: |
| | | Symbols::Variables::Type type_; |
| | | public: |
| | | VariableSymbol(const std::string & name, const Symbols::Value & value, const std::string & context, |
| | | Variables::Type type) : |
| | | Symbol(name, value, context, Symbols::Kind::Variable), |
| | | type_(type) {} |
| | | |
| | | Symbols::Kind kind() const override { return Symbols::Kind::Variable; } |
| | | |
| | | Variables::Type type() const { return type_; } |
| | | |
| | | std::string toString() const { |
| | | std::string r = "VariableSymbol: " + name_ + " Type: " + Symbols::Variables::TypeToString(type_); |
| | | if (type_ == Symbols::Variables::Type::INTEGER) { |
| | | r += " Value: " + std::to_string(value_.get<int>()); |
| | | } else if (type_ == Symbols::Variables::Type::DOUBLE) { |
| | | r += " Value: " + std::to_string(value_.get<double>()); |
| | | } else if (type_ == Symbols::Variables::Type::FLOAT) { |
| | | r += " Value: " + std::to_string(value_.get<float>()); |
| | | } else if (type_ == Symbols::Variables::Type::STRING) { |
| | | r += " Value: " + value_.get<std::string>(); |
| | | } else if (type_ == Symbols::Variables::Type::BOOLEAN) { |
| | | r += " Value: " + std::to_string(value_.get<bool>()); |
| | | } else if (type_ == Symbols::Variables::Type::NULL_TYPE) { |
| | | r += " Value: null"; |
| | | } |
| | | |
| | | return r; |
| | | } |
| | | }; |
| | | |
| | | } // namespace Symbols |
| | | |
| | | #endif |
| New file |
| | |
| | | #ifndef VARIABLE_TYPES_HPP |
| | | #define VARIABLE_TYPES_HPP |
| | | |
| | | #include <cstdint> |
| | | #include <string> |
| | | #include <unordered_map> |
| | | |
| | | namespace Symbols::Variables { |
| | | |
| | | enum class Type : std::uint8_t { INTEGER, DOUBLE, FLOAT, STRING, BOOLEAN, NULL_TYPE, UNDEFINED_TYPE }; |
| | | |
| | | const std::unordered_map<std::string, Type> StringToTypeMap = { |
| | | { "int", Type::INTEGER }, |
| | | { "double", Type::DOUBLE }, |
| | | { "float", Type::FLOAT }, |
| | | { "string", Type::STRING }, |
| | | { "bool", Type::BOOLEAN }, |
| | | { "boolean", Type::BOOLEAN }, |
| | | { "null", Type::NULL_TYPE }, |
| | | |
| | | { "undefined", Type::UNDEFINED_TYPE }, |
| | | }; |
| | | const std::unordered_map<Type, std::string> StypeToStringMap = { |
| | | { Type::INTEGER, "int" }, |
| | | { Type::DOUBLE, "double" }, |
| | | { Type::FLOAT, "float" }, |
| | | { Type::STRING, "string" }, |
| | | { Type::BOOLEAN, "bool" }, |
| | | { Type::NULL_TYPE, "null" }, |
| | | { Type::UNDEFINED_TYPE, "undeffined" }, |
| | | }; |
| | | |
| | | inline static std::string TypeToString(Symbols::Variables::Type type) { |
| | | if (StypeToStringMap.find(type) != StypeToStringMap.end()) { |
| | | return StypeToStringMap.at(type); |
| | | } |
| | | return "null"; |
| | | }; |
| | | |
| | | inline static Type StringToType(const std::string & type) { |
| | | if (StringToTypeMap.find(type) != StringToTypeMap.end()) { |
| | | return StringToTypeMap.at(type); |
| | | } |
| | | return Type::NULL_TYPE; |
| | | }; |
| | | |
| | | }; // namespace Symbols::Variables |
| | | #endif // VARIABLE_TYPES_HPP |
| New file |
| | |
| | | #ifndef VOIDSCRIPT_HPP |
| | | #define VOIDSCRIPT_HPP |
| | | |
| | | #include <filesystem> |
| | | #include <fstream> |
| | | #include <string> |
| | | |
| | | #include "Lexer/Lexer.hpp" |
| | | #include "Parser/Parser.hpp" |
| | | |
| | | class VoidScript { |
| | | private: |
| | | std::string file; |
| | | std::shared_ptr<Lexer::Lexer> lexer = nullptr; |
| | | std::shared_ptr<Parser::Parser> parser = nullptr; |
| | | std::string file_content; |
| | | |
| | | public: |
| | | VoidScript(const std::string & file) : file(file) { |
| | | if (!std::filesystem::exists(file)) { |
| | | throw std::runtime_error("File " + file + " does not exits"); |
| | | } |
| | | lexer = std::make_shared<Lexer::Lexer>(); |
| | | parser = std::make_shared<Parser::Parser>(); |
| | | |
| | | lexer->setKeyWords(Parser::Parser::keywords); |
| | | |
| | | // read in the file |
| | | std::ifstream input(file, std::ios::in); |
| | | if (!input.is_open()) { |
| | | throw std::runtime_error("Could not open file " + file); |
| | | } |
| | | file_content = std::string((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); |
| | | input.close(); |
| | | } |
| | | |
| | | int run() { |
| | | try { |
| | | this->lexer->addNamespaceInput(this->file, this->file_content); |
| | | const auto tokens = this->lexer->tokenizeNamespace(this->file); |
| | | |
| | | std::cout << "--- Tokens ---" << '\n'; |
| | | for (const auto & token : tokens) { |
| | | if (token.type != Lexer::Tokens::Type::END_OF_FILE) { |
| | | //token.print(); |
| | | } |
| | | } |
| | | std::cout << "--------------" << '\n'; |
| | | |
| | | parser->parseProgram(tokens, this->file_content); |
| | | const std::shared_ptr<Symbols::SymbolContainer> & symbol_container = parser->getSymbolContainer(); |
| | | |
| | | std::cout << "\n--- Defined Variables ---" << '\n'; |
| | | for (const auto & symbol_ptr : symbol_container->listNamespace("variables")) { |
| | | // Itt lehetne a szimbólumokat kiírni vagy tovább feldolgozni |
| | | // Szükséges lehet dynamic_cast<> a konkrét típushoz (VariableSymbol) |
| | | if (auto var_symbol = std::dynamic_pointer_cast<Symbols::VariableSymbol>(symbol_ptr)) { |
| | | std::cout << var_symbol->toString() << '\n'; |
| | | } |
| | | } |
| | | |
| | | std::cout << "\n--- Defined Functions ---" << '\n'; |
| | | for (const auto & symbol_ptr : symbol_container->listNamespace("functions")) { |
| | | if (auto func_symbol = std::dynamic_pointer_cast<Symbols::FunctionSymbol>(symbol_ptr)) { |
| | | std::cout << "Func Name: " << func_symbol->name() |
| | | << " return type: " << Symbols::Variables::TypeToString(func_symbol->returnType()) |
| | | << '\n'; |
| | | for (const auto & func_param : func_symbol->parameters()) { |
| | | std::cout << " Param: " << func_param.name |
| | | << " Type: " << Symbols::Variables::TypeToString(func_param.type) << '\n'; |
| | | } |
| | | std::cout << " Context name: " << func_symbol->context() << '\n'; |
| | | std::cout << " Plain body: " << func_symbol->plainBody() << '\n'; |
| | | } |
| | | } |
| | | |
| | | return 0; |
| | | } catch (const Parser::SyntaxError & e) { |
| | | std::cerr << "Syntax Error during parsing: " << e.what() << '\n'; |
| | | return 1; |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "An error occurred: " << e.what() << '\n'; |
| | | return 1; |
| | | } |
| | | return 1; |
| | | } |
| | | }; // class VoidScript |
| | | |
| | | #endif // VOIDSCRIPT_HPP |