some clean up, added function declaration
13 files modified
2 files renamed
4 files added
| | |
| | | set(CMAKE_CXX_STANDARD_REQUIRED ON) |
| | | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) |
| | | |
| | | set(NEED_CLI ON) |
| | | set(NEED_TESTS OFF) |
| | | set(NEED_STATIC_LIBS ON) |
| | | set(NEED_SHARED_LIBS ON) |
| | | |
| | | option(BUILD_CLI, "Build example commandline intrepeter" ON) |
| | | option(BUILD_TESTS, "Build tests" OFF) |
| | | option(BUILD_SHARED_LIBS "Build shared library" ON) |
| | | option(BUILD_STATIC_LIBS "Build static library" ON) |
| | | option(BUILD_CLI, "Build example commandline intrepeter" ${NEED_CLI}) |
| | | option(BUILD_TESTS, "Build tests" ${NEED_TESTS}) |
| | | option(BUILD_SHARED_LIBS "Build shared library" ${NEED_SHARED_LIBS}) |
| | | option(BUILD_STATIC_LIBS "Build static library" ${NEED_STATIC_LIBS}) |
| | | |
| | | if (BUILD_CLI) |
| | | set(NEED_CLI ${BUILD_CLI}) |
| | | endif() |
| | | if (BUILD_TESTS) |
| | | set(NEED_TESTS ${BUILD_TESTS}) |
| | | endif() |
| | | |
| | | if (BUILD_SHARED_LIBS) |
| | | set(NEED_SHARED_LIBS ${BUILD_SHARED_LIBS}) |
| | | endif() |
| | | |
| | | if (BUILD_STATIC_LIBS) |
| | | set(NEED_STATIC_LIBS ${BUILD_STATIC_LIBS}) |
| | | endif() |
| | | |
| | | set(COMMENT_CHARACTER "#") |
| | | set(PARSER_OPEN_TAG "<?void") |
| | | set(PARSER_CLOSE_TAG "?>") |
| | | |
| | | message(STATUS "BUILD_CLI: ${BUILD_CLI}") |
| | | message(STATUS "BUILD_TESTS: ${BUILD_TESTS}") |
| | | message(STATUS "\tCOMMENT_CHARACTER: ${COMMENT_CHARACTER}") |
| | | message(STATUS "BUILD_CLI: ${NEED_CLI}") |
| | | message(STATUS "BUILD_TESTS: ${NEED_TESTS}") |
| | | message(STATUS "BUILD_SHARED_LIBS: ${NEED_SHARED_LIBS}") |
| | | message(STATUS "BUILD_STATIC_LIBS: ${NEED_STATIC_LIBS}") |
| | | message(STATUS " COMMENT_CHARACTER: ${COMMENT_CHARACTER}") |
| | | message(STATUS " PARSER_OPEN_TAG: ${PARSER_OPEN_TAG}") |
| | | message(STATUS " PARSER_CLOSE_TAG: ${PARSER_CLOSE_TAG}") |
| | | |
| | | if (CMAKE_BUILD_TYPE STREQUAL "") |
| | | set(CMAKE_BUILD_TYPE Release) |
| | | message(STATUS "CMAKE_BUILD_TYPE is not set, defaulting to Release") |
| | | set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) |
| | | endif() |
| | | |
| | | |
| | | |
| | | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") |
| | | |
| | | if (CMAKE_BUILD_TYPE STREQUAL "Debug") |
| | | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") |
| | | set(DEBUG_BUILD ON) |
| | | endif() |
| | | |
| | | configure_file("cmake/options.h.in" "include/options.h" @ONLY) |
| | | 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) |
| | | include_directories(src) |
| | | |
| | |
| | | src/Lexer.cpp |
| | | ) |
| | | |
| | | if (BUILD_SHARED_LIBS) |
| | | if (NEED_SHARED_LIBS) |
| | | add_library(${CMAKE_PROJECT_NAME} SHARED) |
| | | target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${SOURCES}) |
| | | set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME}) |
| | | endif() |
| | | if (BUILD_STATIC_LIBS) |
| | | if (NEED_STATIC_LIBS) |
| | | add_library(${CMAKE_PROJECT_NAME}_static STATIC) |
| | | target_sources(${CMAKE_PROJECT_NAME}_static PRIVATE ${SOURCES}) |
| | | set_target_properties(${CMAKE_PROJECT_NAME}_static PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME}) |
| | |
| | | |
| | | set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) |
| | | |
| | | if (BUILD_CLI) |
| | | if (NEED_CLI) |
| | | add_executable(cli cli/main.cpp) |
| | | add_dependencies(cli ${CMAKE_PROJECT_NAME}) |
| | | if (BUILD_SHARED_LIBS) |
| | |
| | | #include <fstream> |
| | | |
| | | #include "Builtins/PrintModule.hpp" |
| | | #include "Builtins/SleepModule.hpp" |
| | | #include "ScriptInterpreter.hpp" |
| | | |
| | | static bool DEBUG = false; |
| | |
| | | std::string file; |
| | | if (argc == 2) { |
| | | file = argv[1]; |
| | | } else if (argc == 3 && (std::string(argv[1]) == "-d" || std::string(argv[1]) == "--debug")) { |
| | | } else if (argc == 3) { |
| | | if (std::string(argv[1]) == "-d" || std::string(argv[1]) == "--debug") { |
| | | DEBUG = true; |
| | | file = argv[2]; |
| | | } else if (argv[1] == "-h" || argv[1] == "--help") { |
| | | std::cout << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n"; |
| | | return 0; |
| | | } else if (argv[1] == "-v" || argv[1] == "--vrsion") { |
| | | std::cout << "VoidScript v" << VERSION_STRING << "\n"; |
| | | return 0; |
| | | } else { |
| | | std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n"; |
| | | return 1; |
| | | } |
| | | } else { |
| | | std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n"; |
| | | return 1; |
| | |
| | | |
| | | std::string content((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); |
| | | ScriptInterpreter interp; |
| | | interp.registerFunction("print", std::make_shared<PrintFunction>()); |
| | | interp.registerModule("print", std::make_shared<PrintFunction>()); |
| | | interp.registerModule("sleep", std::make_shared<SleepFunction>()); |
| | | interp.executeScript(content, filename, DEBUG); |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "Parser error: " << e.what() << "\n"; |
| | |
| | | |
| | | |
| | | #ifndef VOIDSCRIPT_OPTIONS_H |
| | | #define VOIDSCRIPT_OPTIONS_H |
| | | /* |
| | | * THIS IS A GENERATED FILE. DO NOT EDIT. |
| | | */ |
| | |
| | | const char EOL = '\n'; |
| | | #endif |
| | | const char COMMENT_CHARACTER = '@COMMENT_CHARACTER@'; |
| | | const static char * PARSER_OPEN_TAG = "<?void"; |
| | | const static char * PARSER_CLOSE_TAG = "?>"; |
| | | const static char * PARSER_OPEN_TAG = "@PARSER_OPEN_TAG@"; |
| | | const static char * PARSER_CLOSE_TAG = "@PARSER_CLOSE_TAG@"; |
| | | const static char * VERSION_MINOR = "@CMAKE_PROJECT_VERSION_MINOR@"; |
| | | const static char * VERSION_MAJOR = "@CMAKE_PROJECT_VERSION_MAJOR@"; |
| | | const static char * VERSION_PATCH = "@CMAKE_PROJECT_VERSION_PATCH@"; |
| | | const static char * VERSION_STRING = "@CMAKE_PROJECT_VERSION@-@CMAKE_BUILD_TYPE@-@CMAKE_SYSTEM@"; |
| | | #cmakedefine DEBUG_BUILD |
| | | #cmakedefine01 DEBUG_BUILD |
| | | |
| | | #endif // VOIDSCRIPT_OPTIONS_H |
| | |
| | | // ScriptFunction.hpp |
| | | #ifndef SCRIPT_FUNCTION_HPP |
| | | #define SCRIPT_FUNCTION_HPP |
| | | |
| | | #include <functional> |
| | | #include <stdexcept> |
| | | #include <unordered_map> |
| | | #include <utility> |
| | | #include <vector> |
| | | |
| | | #include "ScriptExceptionMacros.h" |
| | | #include "ScriptInterpreterHelpers.hpp" |
| | | #include "Token.hpp" |
| | | #include "Value.hpp" |
| | | |
| | | |
| | | class ScriptInterpreter; |
| | | |
| | | using CallbackFunction = std::function<Value(const std::vector<Value> &)>; |
| | | using CallBackStorage = std::unordered_map<std::string, CallbackFunction>; |
| | | |
| | | class BaseFunction { |
| | | protected: |
| | | std::string name; |
| | | CallBackStorage functionMap; |
| | | |
| | | |
| | | public: |
| | | virtual ~BaseFunction() = default; |
| | | virtual void validate(const std::vector<Token> & tokens, size_t & i) const = 0; |
| | | BaseFunction(const std::string & functionName) : name(functionName) {} |
| | | |
| | | virtual void addFunction(const std::string & name, std::function<Value(const std::vector<Value> &)> callback) { |
| | | functionMap[name] = std::move(callback); |
| | | } |
| | | |
| | | virtual void validate(const std::vector<Token> & tokens, size_t & i, |
| | | const std::unordered_map<std::string, Value> & variables) { |
| | | size_t index = i; |
| | | |
| | | if (tokens[index].type != TokenType::Identifier) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "identifier"); |
| | | } |
| | | index++; // skip function name |
| | | |
| | | if (tokens[index].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "("); |
| | | } |
| | | index++; // skip '(' |
| | | |
| | | std::vector<Token> args; |
| | | while (tokens[index].type != TokenType::RightParenthesis) { |
| | | if (tokens[index].type == TokenType::Comma) { |
| | | index++; |
| | | continue; |
| | | } |
| | | if (tokens[index].type == TokenType::Variable && !variables.contains(tokens[index].lexeme)) { |
| | | THROW_UNDEFINED_VARIABLE_ERROR(tokens[index].lexeme, tokens[index]); |
| | | } |
| | | args.push_back(tokens[index]); |
| | | index++; |
| | | } |
| | | |
| | | index++; // skip ')' |
| | | |
| | | if (tokens[index].type != TokenType::Semicolon) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], ";"); |
| | | } |
| | | |
| | | this->validateArgs(args, variables); |
| | | ScriptInterpreterHelpers::expectSemicolon(tokens, index, "function call"); |
| | | } |
| | | |
| | | virtual void validateArgs(const std::vector<Token> & args, |
| | | const std::unordered_map<std::string, Value> & variables) = 0; |
| | | |
| | | virtual Value call(const std::vector<Value> & args, bool debug = false) const = 0; |
| | | |
| | | template <typename FuncClass> void registerFunctionTo(ScriptInterpreter & interp) { |
| | | FuncClass::registerTo(interp); |
| | | } |
| | | template <typename FuncClass> void registerFunctionTo(ScriptInterpreter & interp) { FuncClass::registerTo(interp); } |
| | | }; |
| | | |
| | | #endif // SCRIPT_FUNCTION_HPP |
| New file |
| | |
| | | #ifndef MATH_UTILS_MODULE_HPP |
| | | #define MATH_UTILS_MODULE_HPP |
| | | |
| | | #include <stdexcept> |
| | | #include <vector> |
| | | |
| | | #include "ScriptExceptionMacros.h" |
| | | #include "Value.hpp" |
| | | |
| | | class MathUtils { |
| | | public: |
| | | static Value multiply(const std::vector<Value> & args) { |
| | | if (args.size() != 2) { |
| | | throw std::runtime_error("multiply expects two arguments."); |
| | | } |
| | | if (args[0].type == Variables::Type::VT_INT && args[1].type == Variables::Type::VT_INT) { |
| | | int left = args[0].ToInt(); |
| | | int right = args[1].ToInt(); |
| | | auto result = Value(); |
| | | result.data = left * right; |
| | | result.type = Variables::Type::VT_INT; |
| | | return result; |
| | | //return Value::fromInt(left * right); |
| | | } |
| | | THROW_INVALID_FUNCTION_ARGUMENT_ERROR("multiply", args[0].TypeToString(), args[0].GetToken()); |
| | | }; |
| | | }; |
| | | #endif |
| | |
| | | |
| | | #include "BaseFunction.hpp" |
| | | #include "ScriptExceptionMacros.h" |
| | | #include "ScriptInterpreter.hpp" |
| | | #include "Token.hpp" |
| | | #include "Value.hpp" |
| | | |
| | | class PrintFunction : public BaseFunction { |
| | | private: |
| | | const std::string name = "print"; |
| | | bool addNewLine = false; |
| | | public: |
| | | void validate(const std::vector<Token> & tokens, size_t & i) const override { |
| | | auto index = i; |
| | | if (tokens[index].type != TokenType::Identifier) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "identifier: " + name); |
| | | PrintFunction() : BaseFunction(name) {} |
| | | |
| | | void validateArgs(const std::vector<Token> & args, |
| | | const std::unordered_map<std::string, Value> & variables) override { |
| | | if (args.size() == 0) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(args[0], "at least one argument"); |
| | | } |
| | | index++; // skip function name |
| | | if (tokens[index].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "('"); |
| | | } |
| | | index++; // skip '(' |
| | | if (tokens[index].type != TokenType::StringLiteral && tokens[index].type != TokenType::Variable && |
| | | tokens[index].type != TokenType::IntLiteral && tokens[index].type != TokenType::DoubleLiteral) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "string, int, double or variable as argument"); |
| | | } |
| | | size_t count = 0; |
| | | while (tokens[index].type != TokenType::RightParenthesis) { |
| | | if (tokens[index].type == TokenType::StringLiteral || tokens[index].type == TokenType::Variable || |
| | | tokens[index].type == TokenType::IntLiteral || tokens[index].type == TokenType::DoubleLiteral) { |
| | | count++; |
| | | index++; |
| | | } else if (tokens[index].type == TokenType::Comma) { |
| | | index++; |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "string, int, double or variable as argument"); |
| | | |
| | | for (const auto & arg : args) { |
| | | if (arg.type == TokenType::Variable) { |
| | | if (!variables.contains(arg.lexeme)) { |
| | | THROW_UNDEFINED_VARIABLE_ERROR(arg.lexeme, arg); |
| | | } |
| | | } |
| | | if (count == 0) { |
| | | throw std::runtime_error("print() requires at least one argument at"); |
| | | } |
| | | index++; // skip ')' |
| | | if (tokens[index].type == TokenType::Semicolon) { |
| | | index++; |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], ";"); |
| | | if (args.end()->variableType == Variables::Type::VT_INT || args.end()->type == TokenType::IntLiteral) { |
| | | this->addNewLine = true; |
| | | } |
| | | } |
| | | |
| | | Value call(const std::vector<Value> & args, bool debug = false) const override { |
| | | for (const auto & arg : args) { |
| | | std::cout << arg.ToString(); |
| | | std::cout << arg.ToString(); // todo: add endline if the last parameter is bool |
| | | } |
| | | return Value(); |
| | | } |
| New file |
| | |
| | | #ifndef SLEEPFUNCTION_HPP |
| | | #define SLEEPFUNCTION_HPP |
| | | |
| | | #include <thread> |
| | | |
| | | #include "BaseFunction.hpp" |
| | | |
| | | class SleepFunction : public BaseFunction { |
| | | public: |
| | | SleepFunction() : BaseFunction("sleep") {} |
| | | |
| | | void validateArgs(const std::vector<Token> & args, |
| | | const std::unordered_map<std::string, Value> & variables) override { |
| | | if (args.size() != 1) { |
| | | throw std::runtime_error("sleep() requires exactly one argument"); |
| | | } |
| | | |
| | | const Token & arg = args[0]; |
| | | |
| | | if (arg.type == TokenType::IntLiteral) { |
| | | return; |
| | | } |
| | | |
| | | if (arg.type == TokenType::Variable) { |
| | | const auto & value = variables.at(arg.lexeme); |
| | | if (value.type != Variables::Type::VT_INT) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(arg.lexeme, Variables::TypeToString(Variables::Type::VT_INT), "", |
| | | Variables::TypeToString(value.type), arg); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | THROW_UNEXPECTED_TOKEN_ERROR(arg, "int literal or variable"); |
| | | } |
| | | |
| | | |
| | | Value call(const std::vector<Value> & args, bool debug = false) const override { |
| | | std::this_thread::sleep_for(std::chrono::seconds(args[0].ToInt())); |
| | | return Value(); |
| | | } |
| | | }; |
| | | |
| | | #endif // SLEEPFUNCTION_HPP |
| | |
| | | |
| | | #include <cctype> |
| | | |
| | | #include "Value.hpp" |
| | | #include "options.h" |
| | | |
| | | Lexer::Lexer(const std::string & source, const std::string & filename) : |
| | | src(source), |
| | |
| | | while (isalpha(peek())) { |
| | | lexeme += advance(); |
| | | } |
| | | |
| | | if (Variables::StringToTypeMap.contains(lexeme)) { |
| | | const auto type = Variables::StringToTypeMap.at(lexeme); |
| | | auto it = Variables::StringToTypeMap.find(lexeme); |
| | | if (it != Variables::StringToTypeMap.end()) { |
| | | const auto & type = it->second; |
| | | while (isspace(peek())) { |
| | | advance(); |
| | | } |
| | | if (peek() == '$') { |
| | | return variableDeclaration(type); |
| | | return this->variableDeclaration(type); |
| | | } |
| | | return { TokenType::Identifier, lexeme, filename, lineNumber, startCol }; |
| | | } |
| | | |
| | | return { TokenType::Identifier, lexeme, filename, lineNumber, startCol }; |
| | | } |
| | | |
| | |
| | | while (isalnum(peek()) || peek() == '_') { |
| | | varName += advance(); |
| | | } |
| | | switch (type) { |
| | | case Variables::Type::VT_INT: |
| | | return { TokenType::IntDeclaration, varName, filename, lineNumber, startCol }; |
| | | case Variables::Type::VT_DOUBLE: |
| | | return { TokenType::DoubleDeclaration, varName, filename, lineNumber, startCol }; |
| | | case Variables::Type::VT_STRING: |
| | | return { TokenType::StringDeclaration, varName, filename, lineNumber, startCol }; |
| | | default: |
| | | for (auto it = Variables::StringToTypeMap.begin(); it != Variables::StringToTypeMap.end(); ++it) { |
| | | if (it->second == type) { |
| | | return { getTokenTypeFromValueDeclaration(it->second), varName, filename, lineNumber, startCol }; |
| | | } |
| | | } |
| | | |
| | | return { TokenType::Unknown, "Invalid variable type in declaration", filename, lineNumber, startCol }; |
| | | } |
| | | } else { |
| | | return { TokenType::Unknown, "$ followed by invalid character in declaration", filename, lineNumber, startCol }; |
| | | } |
| | | } |
| | | |
| | | Token Lexer::singleCharToken(TokenType type, const std::string & lexeme) { |
| | |
| | | return { type, lexeme, filename, lineNumber, startCol }; |
| | | } |
| | | |
| | | bool Lexer::matchSequence(const std::string & sequence) const { |
| | | if (pos + sequence.length() > src.length()) { |
| | | bool Lexer::matchSequence(const std::string & sequence, bool caseSensitive) const { |
| | | if (this->pos + sequence.size() > src.size()) { |
| | | return false; |
| | | } |
| | | return src.substr(pos, sequence.length()) == sequence; |
| | | |
| | | for (size_t i = 0; i < sequence.size(); ++i) { |
| | | char srcChar = src[this->pos + i]; |
| | | char seqChar = sequence[i]; |
| | | |
| | | if (!caseSensitive) { |
| | | srcChar = std::tolower(static_cast<unsigned char>(srcChar)); |
| | | seqChar = std::tolower(static_cast<unsigned char>(seqChar)); |
| | | } |
| | | |
| | | void Lexer::matchAndConsume(const std::string & sequence) { |
| | | if (matchSequence(sequence)) { |
| | | if (srcChar != seqChar) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | void Lexer::matchAndConsume(const std::string & sequence, bool caseSensitive) { |
| | | if (matchSequence(sequence, caseSensitive)) { |
| | | for (size_t i = 0; i < sequence.length(); ++i) { |
| | | advance(); |
| | | } |
| | |
| | | |
| | | std::vector<Token> Lexer::tokenize() { |
| | | std::vector<Token> tokens; |
| | | tokens.reserve(src.size() / 4); |
| | | |
| | | while (!isAtEnd()) { |
| | | char c = peek(); |
| | | while (pos < src.size()) { |
| | | char c = src[pos]; |
| | | if (isspace(c)) { |
| | | advance(); |
| | | continue; |
| | | } |
| | | if (c == '\n') { |
| | | tokens.push_back(singleCharToken(TokenType::EndOfLine, "")); |
| | | tokens.push_back(singleCharToken(TokenType::EndOfLine, "\n")); |
| | | continue; |
| | | } |
| | | if (c == '#') { |
| | | if (c == COMMENT_CHARACTER) { |
| | | tokens.push_back(comment()); |
| | | advance(); // Skip newline after comment |
| | | continue; |
| | |
| | | tokens.push_back({ TokenType::ParserCloseTag, PARSER_CLOSE_TAG, filename, lineNumber, startCol }); |
| | | continue; |
| | | } |
| | | if (isalpha(c)) { |
| | | if (matchSequence("if")) { |
| | | size_t startCol = colNumber; |
| | | matchAndConsume("if"); |
| | | tokens.push_back({ TokenType::ParserIfStatement, "if", filename, lineNumber, startCol }); |
| | | continue; |
| | | } |
| | | |
| | | switch (c) { |
| | | case 'a' ... 'z': |
| | | case 'A' ... 'Z': |
| | | tokens.push_back(keywordOrIdentifier()); |
| | | } else if (c == '$') { |
| | | break; |
| | | case '$': |
| | | tokens.push_back(variable()); |
| | | } else if (isdigit(c)) { |
| | | break; |
| | | case '0' ... '9': |
| | | tokens.push_back(number()); |
| | | } else if (c == '"' || c == '\'') { |
| | | break; |
| | | case '"': |
| | | case '\'': |
| | | tokens.push_back(string()); |
| | | } else if (c == '(') { |
| | | break; |
| | | case '(': |
| | | tokens.push_back(singleCharToken(TokenType::LeftParenthesis, "(")); |
| | | } else if (c == ')') { |
| | | break; |
| | | case ')': |
| | | tokens.push_back(singleCharToken(TokenType::RightParenthesis, ")")); |
| | | } else if (c == ',') { |
| | | break; |
| | | case ',': |
| | | tokens.push_back(singleCharToken(TokenType::Comma, ",")); |
| | | } else if (c == ';') { |
| | | break; |
| | | case ';': |
| | | tokens.push_back(singleCharToken(TokenType::Semicolon, ";")); |
| | | } else if (c == '=') { |
| | | break; |
| | | case '=': |
| | | tokens.push_back(singleCharToken(TokenType::Equals, "=")); |
| | | } else { |
| | | break; |
| | | case '+': |
| | | tokens.push_back(singleCharToken(TokenType::Plus, "+")); |
| | | break; |
| | | case '{': |
| | | tokens.push_back(singleCharToken(TokenType::LeftCurlyBracket, "{")); |
| | | break; |
| | | case '}': |
| | | tokens.push_back(singleCharToken(TokenType::RightCurlyBracket, "}")); |
| | | break; |
| | | default: |
| | | tokens.push_back({ TokenType::Unknown, std::string(1, c), filename, lineNumber, colNumber }); |
| | | advance(); |
| | | break; |
| | | } |
| | | } |
| | | |
| | |
| | | #ifndef LEXER_HPP |
| | | #define LEXER_HPP |
| | | |
| | | #include <algorithm> |
| | | #include <istream> |
| | | #include <sstream> |
| | | #include <vector> |
| | | |
| | | #include "VariableTypes.hpp" |
| | | #include "options.h" |
| | | #include "Token.hpp" |
| | | #include "VariableTypes.hpp" |
| | | |
| | | class Lexer { |
| | | public: |
| | |
| | | Token variable(); |
| | | Token comment(); |
| | | Token keywordOrIdentifier(); |
| | | Token boolean(); |
| | | Token singleCharToken(TokenType type, const std::string & lexeme); |
| | | bool matchSequence(const std::string & sequence) const; |
| | | bool matchSequence(const std::string & sequence, bool caseSensitive = true) const; |
| | | Token variableDeclaration(Variables::Type type); |
| | | void matchAndConsume(const std::string & sequence); |
| | | |
| | | void matchAndConsume(const std::string & sequence, bool caseSensitive = true); |
| | | |
| | | // validate number types from string |
| | | template <typename Numeric> static bool is_number(const std::string & s) { |
| | |
| | | return ((std::istringstream(s) >> n >> std::ws).eof()); |
| | | } |
| | | |
| | | bool matchSequence(const std::string & sequence) { return src.substr(pos, sequence.length()) == sequence; } |
| | | bool matchSequence(const std::string & sequence, bool caseSensitive = true) { |
| | | if (caseSensitive) { |
| | | return src.substr(pos, sequence.length()) == sequence; |
| | | } |
| | | |
| | | std::string srcSubstr = src.substr(pos, sequence.length()); |
| | | std::string seqLower = sequence; |
| | | |
| | | std::transform(srcSubstr.begin(), srcSubstr.end(), srcSubstr.begin(), |
| | | [](unsigned char c) { return std::tolower(c); }); |
| | | |
| | | std::transform(seqLower.begin(), seqLower.end(), seqLower.begin(), |
| | | [](unsigned char c) { return std::tolower(c); }); |
| | | |
| | | return srcSubstr == seqLower; |
| | | } |
| | | }; |
| | | |
| | | #endif // LEXER_HPP |
| New file |
| | |
| | | #ifndef SCRIPTEXCEPTION_HPP |
| | | #define SCRIPTEXCEPTION_HPP |
| | | |
| | | #include <stdexcept> |
| | | #include <string> |
| | | |
| | | #include "options.h" |
| | | #include "Token.hpp" |
| | | |
| | | enum class ScriptErrorType : std::uint8_t { |
| | | UnexpectedToken, |
| | | UndefinedVariable, |
| | | UndefinedFunction, |
| | | VariableTypeMismatch, |
| | | VariableRedefinition, |
| | | Custom |
| | | }; |
| | | |
| | | class ScriptException : public std::runtime_error { |
| | | public: |
| | | ScriptException(ScriptErrorType type, const std::string & message, const std::string & file = "", int line = 0, |
| | | const Token & token = Token()) : |
| | | std::runtime_error(message), |
| | | type_(type), |
| | | file_(file), |
| | | line_(line), |
| | | token_(token), |
| | | fullMessage_(formatMessage(message)) {} |
| | | |
| | | const char * what() const noexcept override { return fullMessage_.c_str(); } |
| | | |
| | | ScriptErrorType type() const { return type_; } |
| | | |
| | | const std::string & file() const { return file_; } |
| | | |
| | | int line() const { return line_; } |
| | | |
| | | const Token & token() const { return token_; } |
| | | |
| | | static ScriptException makeUnexpectedTokenError(const Token & token, const std::string & expected = "", |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "unexpected token: '" + token.lexeme + "'"; |
| | | |
| | | #if DEBUG_BUILD == 1 |
| | | msg.append(" token type: " + tokenTypeNames.at(token.type)); |
| | | #endif |
| | | |
| | | if (!expected.empty()) { |
| | | msg += ", expected: '" + expected + "'"; |
| | | } |
| | | return ScriptException(ScriptErrorType::UnexpectedToken, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeUndefinedVariableError(const std::string & name, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "undefined variable: '$" + name + "'"; |
| | | return ScriptException(ScriptErrorType::UndefinedVariable, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeUndefinedFunctionError(const std::string & name, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "undefined function: '" + name + "'"; |
| | | #if DEBUG_BUILD == 1 |
| | | msg.append(", type: " + tokenTypeNames.at(token.type)); |
| | | #endif |
| | | return ScriptException(ScriptErrorType::UndefinedFunction, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeVariableRedefinitionError(const std::string & name, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "variable already defined: '" + name + "'"; |
| | | return ScriptException(ScriptErrorType::VariableRedefinition, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeVariableTypeMismatchError(const std::string & targetVar, const std::string & targetType, |
| | | const std::string & sourceVar, const std::string & sourceType, |
| | | const Token & token, const std::string & file = "", |
| | | int line = 0) { |
| | | std::string msg = "variable type mismatch: '$" + targetVar + "' declared type: '" + targetType + "'"; |
| | | if (!sourceVar.empty()) { |
| | | msg += ", source variable: '" + sourceVar + "'"; |
| | | } |
| | | if (!sourceType.empty()) { |
| | | msg += ", assigned type: '" + sourceType + "'"; |
| | | } |
| | | return ScriptException(ScriptErrorType::VariableTypeMismatch, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeFunctionRedefinitionError(const std::string & name, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "variable already defined: '" + name + "'"; |
| | | return ScriptException(ScriptErrorType::VariableRedefinition, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeFunctionInvalidArgumentError(const std::string & functionName, |
| | | const std::string & argName, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "invalid argument for function '" + functionName + "': '" + argName + "'"; |
| | | return ScriptException(ScriptErrorType::Custom, msg, file, line, token); |
| | | } |
| | | |
| | | private: |
| | | ScriptErrorType type_; |
| | | std::string file_; |
| | | int line_; |
| | | Token token_; |
| | | std::string fullMessage_; |
| | | |
| | | std::string formatMessage(const std::string & base) const { |
| | | std::string formatted = base; |
| | | if (!token_.file.empty()) { |
| | | formatted += " in file: " + token_.file + ":" + std::to_string(token_.lineNumber) + ":" + |
| | | std::to_string(token_.columnNumber); |
| | | } |
| | | #if DEBUG_BUILD == 1 |
| | | if (!file_.empty()) { |
| | | formatted = file_ + ":" + std::to_string(line_) + "\n" + formatted; |
| | | } |
| | | #endif |
| | | return formatted; |
| | | } |
| | | }; |
| | | |
| | | #endif // SCRIPTEXCEPTION_HPP |
| | |
| | | #ifndef SCRIPT_EXCEPTION_MACROS_H |
| | | #define SCRIPT_EXCEPTION_MACROS_H |
| | | |
| | | #include "ScriptException.hpp" |
| | | |
| | | // |
| | | // Purpose of macros: unified exception handling with extended error information (source file and line number) |
| | | // |
| | | |
| | | // Invalid token type - expected different type |
| | | #define THROW_UNEXPECTED_TOKEN_ERROR(token, expected) \ |
| | | ScriptInterpreter::throwUnexpectedTokenError(token, expected, __FILE__, __LINE__) |
| | | throw ScriptException::makeUnexpectedTokenError(token, expected, __FILE__, __LINE__) |
| | | |
| | | #define THROW_UNEXPECTED_TOKEN_ERROR_HELPER(token, expected, file, line) \ |
| | | throw ScriptException::makeUnexpectedTokenError(token, expected, file, line) |
| | | |
| | | // Accessing unknown (undefined) variable |
| | | #define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \ |
| | | ScriptInterpreter::throwUndefinedVariableError(name, token, __FILE__, __LINE__) |
| | | throw ScriptException::makeUndefinedVariableError(name, token, __FILE__, __LINE__) |
| | | |
| | | // Unknown (undefined) function call |
| | | #define THROW_UNDEFINED_FUNCTION_ERROR(name, token) \ |
| | | throw ScriptException::makeUndefinedFunctionError(name, token, __FILE__, __LINE__) |
| | | |
| | | // Variable type mismatch - e.g. string instead of number |
| | | #define THROW_VARIABLE_TYPE_MISSMATCH_ERROR(target_variable_name, target_variable_type, source_variable_name, \ |
| | | source_variable_type, token) \ |
| | | ScriptInterpreter::throwVariableTypeMissmatchError(target_variable_name, target_variable_type, \ |
| | | throw ScriptException::makeVariableTypeMismatchError(target_variable_name, target_variable_type, \ |
| | | source_variable_name, source_variable_type, token, __FILE__, \ |
| | | __LINE__) |
| | | |
| | | // Redefining a variable with the same name is not allowed |
| | | #define THROW_VARIABLE_REDEFINITION_ERROR(name, token) \ |
| | | ScriptInterpreter::throwVariableRedefinitionError(name, token, __FILE__, __LINE__) |
| | | throw ScriptException::makeVariableRedefinitionError(name, token, __FILE__, __LINE__) |
| | | |
| | | #define THROW_FUNCTION_REDEFINITION_ERROR(name, token) \ |
| | | throw ScriptException::makeFunctionRedefinitionError(name, token, __FILE__, __LINE__) |
| | | |
| | | // Invalid or incorrect function argument |
| | | #define THROW_INVALID_FUNCTION_ARGUMENT_ERROR(functionName, argName, token) \ |
| | | throw ScriptException::makeFunctionInvalidArgumentError(functionName, argName, token, __FILE__, __LINE__) |
| | | |
| | | #endif // SCRIPT_EXCEPTION_MACROS_H |
| | |
| | | #include "ScriptExceptionMacros.h" |
| | | #include "Value.hpp" |
| | | |
| | | void ScriptInterpreter::registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn) { |
| | | void ScriptInterpreter::registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn) { |
| | | functionObjects[name] = std::move(fn); |
| | | } |
| | | |
| | | Value ScriptInterpreter::evaluateExpression(const Token & token) const { |
| | | if (token.type == TokenType::StringLiteral) { |
| | | return Value::fromString(token.lexeme); |
| | | return Value::fromString(token); |
| | | } |
| | | if (token.type == TokenType::IntLiteral) { |
| | | try { |
| | | return Value::fromInt(std::stoi(token.lexeme)); |
| | | return Value::fromInt(token); |
| | | } catch (const std::invalid_argument & e) { |
| | | throw std::runtime_error("Invalid integer literal: " + token.lexeme); |
| | | } catch (const std::out_of_range & e) { |
| | |
| | | } |
| | | if (token.type == TokenType::DoubleLiteral) { |
| | | try { |
| | | return Value::fromDouble(std::stod(token.lexeme)); |
| | | return Value::fromDouble(token); |
| | | } catch (const std::invalid_argument & e) { |
| | | throw std::runtime_error("Invalid double literal: " + token.lexeme); |
| | | } catch (const std::out_of_range & e) { |
| | |
| | | } |
| | | } |
| | | if (token.type == TokenType::Variable) { |
| | | if (variables.find(token.lexeme) != variables.end()) { |
| | | return variables.at(token.lexeme); |
| | | return this->getVariable(token); |
| | | } |
| | | THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable"); |
| | | } |
| | | |
| | | return Value(); |
| | | } |
| | | |
| | | std::vector<Value> ScriptInterpreter::parseArguments(const std::vector<Token> & tokens, |
| | | std::size_t & current_index) const { |
| | | std::vector<Value> ScriptInterpreter::parseFunctionArguments(const std::vector<Token> & tokens, |
| | | std::size_t & index) const { |
| | | std::vector<Value> args; |
| | | size_t current_index = index; |
| | | |
| | | if (current_index >= tokens.size() || tokens[current_index].type != TokenType::Identifier) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], tokenTypeNames.at(TokenType::Identifier)); |
| | | } |
| | | current_index++; // Skip function name |
| | | // if (current_index >= tokens.size() || tokens[current_index].type != TokenType::Identifier) { |
| | | // THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], tokenTypeNames.at(TokenType::Identifier)); |
| | | // } |
| | | // current_index++; // Skip function name |
| | | |
| | | if (current_index >= tokens.size() || tokens[current_index].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], tokenTypeNames.at(TokenType::LeftParenthesis)); |
| | |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], "')'"); |
| | | } |
| | | current_index++; // Skip ')' |
| | | current_index = current_index; |
| | | index = current_index; |
| | | |
| | | return args; |
| | | } |
| | | |
| | | void ScriptInterpreter::handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto varType = tokens[i].variableType; |
| | | i++; // Skip variable name |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Equals) { |
| | | i++; // Skip '=' |
| | | |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Variable) { |
| | | const auto variable = this->getVariable(tokens[i]); |
| | | |
| | | if (variable.type != Variables::Type::VT_BOOLEAN) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_BOOLEAN), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | this->setVariable(varName, variable); |
| | | i++; // Skip variable name |
| | | EXPECT_SEMICOLON(tokens, i, "after bool variable declaration"); |
| | | |
| | | } else if (i < tokens.size() && tokens[i].type == TokenType::Identifier) { |
| | | std::string lowered = tokens[i].lexeme; |
| | | std::transform(lowered.begin(), lowered.end(), lowered.begin(), |
| | | [](unsigned char c) { return std::tolower(c); }); |
| | | |
| | | if (lowered == "true") { |
| | | this->setVariable(varName, Value::fromBoolean(tokens[i], true)); |
| | | } else if (lowered == "false") { |
| | | this->setVariable(varName, Value::fromBoolean(tokens[i], false)); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "true or false after '='"); |
| | | } |
| | | i++; // Skip boolean literal |
| | | EXPECT_SEMICOLON(tokens, i, "after bool declaration"); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='"); |
| | | } |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after bool declaration"); |
| | | } |
| | | }; |
| | | |
| | | void ScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const auto varName = tokens[i].lexeme; |
| | |
| | | i++; // Skip '=' |
| | | |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Variable) { |
| | | if (variables.find(tokens[i].lexeme) == variables.end()) { |
| | | THROW_UNDEFINED_VARIABLE_ERROR(tokens[i].lexeme, tokens[i]); |
| | | } else { |
| | | if (variables[tokens[i].lexeme].type != Variables::Type::VT_STRING) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING), |
| | | tokens[i].lexeme, variables[tokens[i].lexeme].TypeToString(), |
| | | tokens[i]); |
| | | } |
| | | const auto variable = this->getVariable(tokens[i]); |
| | | |
| | | variables[varName] = variables[tokens[i].lexeme]; |
| | | i++; // Skip variable name |
| | | expectSemicolon(tokens, i, "after string variable declaration"); |
| | | if (variable.type != Variables::Type::VT_STRING) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | this->setVariable(varName, variable); |
| | | //variables[varName] = variables[tokens[i].lexeme]; |
| | | i++; // Skip variable name |
| | | EXPECT_SEMICOLON(tokens, i, "after string variable declaration"); |
| | | |
| | | } else if (i < tokens.size() && tokens[i].type == TokenType::StringLiteral) { |
| | | variables[varName] = Value::fromString(tokens[i].lexeme); |
| | | this->setVariable(varName, Value::fromString(tokens[i])); |
| | | i++; // Skip string literal |
| | | expectSemicolon(tokens, i, "after string declaration"); |
| | | EXPECT_SEMICOLON(tokens, i, "after string declaration"); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "string literal after '='"); |
| | | } |
| | |
| | | if (i < tokens.size()) { |
| | | if (type == TokenType::IntDeclaration && tokens[i].type == TokenType::IntLiteral) { |
| | | try { |
| | | if (variables.find(varName) != variables.end()) { |
| | | THROW_VARIABLE_REDEFINITION_ERROR(varName, tokens[i]); |
| | | const auto variable = this->getVariable(tokens[i]); |
| | | if (variable.type != Variables::Type::VT_INT) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_INT), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | variables[varName] = Value::fromInt(std::stoi(tokens[i].lexeme)); |
| | | this->setVariable(varName, Value::fromInt(tokens[i])); |
| | | i++; // Skip int literal |
| | | } catch (const std::invalid_argument & e) { |
| | | throw std::runtime_error("Invalid integer literal in declaration: " + tokens[i].lexeme); |
| | |
| | | } |
| | | } else if (type == TokenType::DoubleDeclaration && tokens[i].type == TokenType::DoubleLiteral) { |
| | | try { |
| | | if (variables.find(varName) != variables.end()) { |
| | | THROW_VARIABLE_REDEFINITION_ERROR(varName, tokens[i]); |
| | | const auto variable = this->getVariable(tokens[i]); |
| | | if (variable.type != Variables::Type::VT_DOUBLE) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, |
| | | Variables::TypeToString(Variables::Type::VT_DOUBLE), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | variables[varName] = Value::fromDouble(std::stod(tokens[i].lexeme)); |
| | | this->setVariable(varName, Value::fromDouble(tokens[i])); |
| | | i++; // Skip double literal |
| | | } catch (const std::invalid_argument & e) { |
| | | throw std::runtime_error("Invalid double literal in declaration: " + tokens[i].lexeme); |
| | |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, expectedType, "", |
| | | getVariableTypeFromTokenTypeAsString(tokens[i].type), tokens[i]); |
| | | } |
| | | expectSemicolon(tokens, i, "after variable declaration"); |
| | | EXPECT_SEMICOLON(tokens, i, "after variable declaration"); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "literal after '='"); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | void ScriptInterpreter::handleFunctionDeclaration(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto varType = tokens[i].variableType; |
| | | |
| | | i++; // skip funct name |
| | | |
| | | if (i < tokens.size() && tokens[i].type != TokenType::Equals) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after function declaration"); |
| | | } |
| | | i++; // skip '=' |
| | | |
| | | if (this->functionParameters.find(varName) != this->functionParameters.end()) { |
| | | THROW_FUNCTION_REDEFINITION_ERROR(varName, tokens[i]); |
| | | } |
| | | |
| | | if (i < tokens.size() && tokens[i].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], ""); |
| | | } |
| | | i++; |
| | | // parse arg definitions |
| | | |
| | | const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i); |
| | | std::cout << "args: " << args.size() << std::endl; |
| | | for (const auto & arg : args) { |
| | | std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << std::endl; |
| | | } |
| | | this->functionParameters[varName].assign(args.begin(), args.end()); |
| | | const std::string body = ScriptInterpreterHelpers::getFunctionBody(tokens, i); |
| | | this->functionBodies[varName] = body; |
| | | // recheck the close curly brace |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}"); |
| | | } |
| | | i++; |
| | | //this->functionBodies[varName] = std::make_shared<ScriptInterpreter>(); |
| | | //this->functionBodies[varName]->executeScript(body, this->filename, true); |
| | | //EXPECT_SEMICOLON(tokens, i, "after function declaration"); |
| | | // there is no need semicolon to the end of the function declaration |
| | | std::cout << "function body: \n\"" << body << "\"" << std::endl; |
| | | } |
| | | |
| | | void ScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) { |
| | | auto index = i; |
| | | std::string funcName = tokens[i].lexeme; |
| | | |
| | | index++; // skip funct name |
| | | if (index < tokens.size() && tokens[index].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index - 1], ""); |
| | | } |
| | | |
| | | auto it = functionObjects.find(funcName); |
| | | if (it == functionObjects.end()) { |
| | | throw std::runtime_error("Unknown function: " + funcName); |
| | | THROW_UNDEFINED_FUNCTION_ERROR(funcName, tokens[i]); |
| | | } |
| | | it->second->validate(tokens, i); |
| | | std::vector<Value> args = parseArguments(tokens, i); |
| | | |
| | | // it->second->validate(tokens, i, this->variables); |
| | | |
| | | std::vector<Value> args = parseFunctionArguments(tokens, index); |
| | | it->second->call(args); |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Semicolon) { |
| | | i++; // Skip ';' after function call |
| | | } |
| | | i = index; |
| | | |
| | | EXPECT_SEMICOLON(tokens, i, "after function call"); |
| | | |
| | | // if (i < tokens.size() && tokens[i].type == TokenType::Semicolon) { |
| | | // i++; // Skip ';' after function call |
| | | // } |
| | | } |
| | | |
| | | void ScriptInterpreter::handleVariableReference(const std::vector<Token> & tokens, std::size_t & i) { |
| | | //THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "function call or variable assignment (not yet implemented)"); |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto varType = tokens[i].variableType; |
| | | // const auto varName = tokens[i].lexeme; |
| | | // const auto varType = tokens[i].variableType; |
| | | const auto& varToken = tokens[i]; |
| | | i++; // Skip variable token to avoid infinite loop |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Equals) { |
| | | i++; // Skip '=' |
| | | if (i < tokens.size()) { |
| | | if (variables.find(varName) == variables.end()) { |
| | | THROW_UNDEFINED_VARIABLE_ERROR(varName, tokens[i]); |
| | | } |
| | | variables[varName] = evaluateExpression(tokens[i]); |
| | | const auto variable = this->getVariable(varToken); |
| | | this->setVariable(varToken.lexeme, evaluateExpression(tokens[i])); |
| | | i++; // Skip value |
| | | expectSemicolon(tokens, i, "after variable assignment"); |
| | | EXPECT_SEMICOLON(tokens, i, "after variable assignment"); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "value after '='"); |
| | | } |
| | |
| | | i++; // Skip semicolon token |
| | | } |
| | | |
| | | void ScriptInterpreter::expectSemicolon(const std::vector<Token> & tokens, std::size_t & i, |
| | | const std::string & message) const { |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::Semicolon) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "; " + message); |
| | | } else { |
| | | i++; // Skip ';' |
| | | } |
| | | } |
| | | |
| | | void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool debug) { |
| | | void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool ignore_tags) { |
| | | this->filename = filename; |
| | | Lexer lexer(source, filename); |
| | | auto tokens = lexer.tokenize(); |
| | | |
| | |
| | | continue; |
| | | } |
| | | |
| | | if (!insideScript) { |
| | | // Csak kiíratás, ha nem vagyunk script tagben |
| | | std::cout << token.lexeme; |
| | | if (insideScript == false && ignore_tags == false) { |
| | | //std::cout << token.lexeme; |
| | | i++; |
| | | continue; |
| | | } |
| | | |
| | | // A szokásos feldolgozás csak ha belül vagyunk |
| | | switch (token.type) { |
| | | case TokenType::StringDeclaration: |
| | | handleStringDeclaration(tokens, i); |
| | | break; |
| | | case TokenType::BooleanDeclaration: |
| | | handleBooleanDeclaration(tokens, i); |
| | | break; |
| | | case TokenType::FunctionDeclaration: |
| | | handleFunctionDeclaration(tokens, i); |
| | | break; |
| | | case TokenType::IntDeclaration: |
| | | case TokenType::DoubleDeclaration: |
| | |
| | | handleSemicolon(i); |
| | | break; |
| | | default: |
| | | throw std::runtime_error("Unexpected token inside script: " + token.lexeme); |
| | | THROW_UNEXPECTED_TOKEN_ERROR(token, ""); |
| | | } |
| | | } |
| | | } |
| | |
| | | #ifndef SSCRIPTINTERPRETER_HPP |
| | | #define SSCRIPTINTERPRETER_HPP |
| | | #include <functional> |
| | | #include <map> |
| | | #include <memory> |
| | | #include <string> |
| | | #include <unordered_map> |
| | |
| | | #include "Token.hpp" |
| | | #include "Value.hpp" |
| | | |
| | | using FunctionValidator = std::function<void(const std::vector<Token> &, size_t &)>; |
| | | using FunctionValidator = |
| | | std::function<void(const std::vector<Token> &, size_t &, const std::unordered_map<std::string, Value> &)>; |
| | | using VariableContext = std::unordered_map<std::string, Value>; |
| | | |
| | | class ScriptInterpreter { |
| | | public: |
| | | void registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn); |
| | | void executeScript(const std::string & source, const std::string & filenaneame, bool debug = false); |
| | | |
| | | static void throwUnexpectedTokenError(const Token & token, const std::string & expected = "", |
| | | const std::string & file = "", const int & line = 0) { |
| | | const std::string error_content = |
| | | "unexpected token: '" + token.lexeme + "' type: " + tokenTypeNames.at(token.type) + |
| | | (expected.empty() ? "" : ", expected: '" + expected + "'") + " in file: " + token.file + ":" + |
| | | std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber); |
| | | #ifdef DEBUG_BUILD |
| | | const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content; |
| | | #else |
| | | const std::string& error_message = error_content; |
| | | #endif |
| | | throw std::runtime_error(error_message); |
| | | }; |
| | | |
| | | static void throwUndefinedVariableError(const std::string & name, const Token & token, const std::string & file, |
| | | const int & line = 0) { |
| | | const std::string error_content = "undefined variable: '$" + name + "' in file: " + token.file + ":" + |
| | | std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber); |
| | | #ifdef DEBUG_BUILD |
| | | const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content; |
| | | #else |
| | | const std::string& error_message = error_content; |
| | | #endif |
| | | throw std::runtime_error(error_message); |
| | | } |
| | | |
| | | static void throwVariableTypeMissmatchError(const std::string & target_variable_name, |
| | | const std::string & target_type, |
| | | const std::string & source_variable_name, |
| | | const std::string & source_type, const Token & token, |
| | | const std::string & file = "", const int & line = 0) { |
| | | std::string error_content = |
| | | "variable type missmatch: '$" + target_variable_name + "' declared type: '" + target_type + "'"; |
| | | if (!source_variable_name.empty()) { |
| | | error_content += " source variable: '" + source_variable_name + "'"; |
| | | } |
| | | if (!source_type.empty()) { |
| | | error_content += " assigned type: '" + source_type + "'"; |
| | | } |
| | | |
| | | error_content += " in file: " + token.file + ":" + std::to_string(token.lineNumber) + ":" + |
| | | std::to_string(token.columnNumber); |
| | | #ifdef DEBUG_BUILD |
| | | const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content; |
| | | #else |
| | | const std::string error_message = error_content; |
| | | #endif |
| | | throw std::runtime_error(error_message); |
| | | } |
| | | |
| | | static void throwVariableRedefinitionError(const std::string & name, const Token & token, |
| | | const std::string & file = "", const int line = 0) { |
| | | const std::string error_content = "variable alread defined: " + name + " in file: " + token.file + ":" + |
| | | std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber); |
| | | #ifdef DEBUG_BUILD |
| | | const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content; |
| | | #else |
| | | const std::string& error_message = error_content; |
| | | #endif |
| | | throw std::runtime_error(error_message); |
| | | } |
| | | void registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn); |
| | | void executeScript(const std::string & source, const std::string & filenaneame, bool ignore_tags = false); |
| | | |
| | | private: |
| | | std::unordered_map<std::string, FunctionValidator> functionValidators; |
| | | std::unordered_map<std::string, std::shared_ptr<BaseFunction>> functionObjects; |
| | | std::unordered_map<std::string, Value> variables; |
| | | // store the current script's variables |
| | | std::unordered_map<std::string, VariableContext> variables; |
| | | // store the current script's function arguments |
| | | std::unordered_map<std::string, std::vector<Value>> functionParameters; |
| | | std::unordered_map<std::string, std::string> functionBodies; |
| | | std::string filename; |
| | | |
| | | std::vector<Value> parseArguments(const std::vector<Token> & tokens, std::size_t & current_index) const; |
| | | Value evaluateExpression(const Token & token) const; |
| | | [[nodiscard]] std::vector<Value> parseFunctionArguments(const std::vector<Token> & tokens, |
| | | std::size_t & index) const; |
| | | [[nodiscard]] Value evaluateExpression(const Token & token) const; |
| | | |
| | | // type handlers |
| | | void expectSemicolon(const std::vector<Token> & tokens, std::size_t & i, const std::string & message) const; |
| | | void setVariable(const std::string & name, const Value & value, const std::string & context = "default") { |
| | | this->variables[context][name] = value; |
| | | } |
| | | |
| | | [[nodiscard]] Value getVariable(const std::string & name, const std::string & context = "default") const { |
| | | for (auto it = variables.begin(); it != variables.end(); ++it) { |
| | | if (it->first == context) { |
| | | const auto & innerMap = it->second.find(name); |
| | | if (innerMap != it->second.end()) { |
| | | return it->second.at(name); |
| | | } |
| | | } |
| | | } |
| | | throw std::runtime_error("Variable not found: " + name); |
| | | }; |
| | | |
| | | [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default") const { |
| | | for (auto it = variables.begin(); it != variables.end(); ++it) { |
| | | if (it->first == context) { |
| | | const auto & innerMap = it->second.find(token.lexeme); |
| | | if (innerMap != it->second.end()) { |
| | | return it->second.at(token.lexeme); |
| | | } |
| | | } |
| | | } |
| | | THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token); |
| | | }; |
| | | |
| | | void handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i); |
| | | void handleVariableReference(const std::vector<Token> & tokens, std::size_t & i); |
| | | void handleComment(std::size_t & i); |
| | | void handleSemicolon(std::size_t & i); |
| | | void handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i); |
| | | void handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i); |
| | | void handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type); |
| | | void handleFunctionDeclaration(const std::vector<Token> & tokens, std::size_t & i); |
| | | }; |
| | | |
| | | #endif // SSCRIPTINTERPRETER_HPP |
| New file |
| | |
| | | #ifndef SCRIPTINTERPRETERHELPERS_HPP |
| | | #define SCRIPTINTERPRETERHELPERS_HPP |
| | | |
| | | #include <iostream> |
| | | #include <ostream> |
| | | #include <vector> |
| | | |
| | | #include "ScriptExceptionMacros.h" |
| | | #include "Token.hpp" |
| | | #include "Value.hpp" |
| | | |
| | | #define EXPECT_SEMICOLON(tokens, i, message) \ |
| | | ScriptInterpreterHelpers::expectSemicolon(tokens, i, message, __FILE__, __LINE__) |
| | | |
| | | namespace ScriptInterpreterHelpers { |
| | | |
| | | static std::string extractSubstring(const std::string & str, size_t start, size_t end) { |
| | | if (start >= 0 && start < str.length() && end >= start && end < str.length()) { |
| | | return str.substr(start, end - start + 1); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | static void expectSemicolon(const std::vector<Token> & tokens, std::size_t & i, const std::string & message, |
| | | const std::string & file = __FILE__, int line = __LINE__) { |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::Semicolon) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i - 1], "; " + message, file, line); |
| | | } |
| | | i++; // Skip ';' |
| | | } |
| | | |
| | | [[nodiscard]] static std::vector<Value> parseFunctionDeclarationArguments(const std::vector<Token> & tokens, |
| | | std::size_t & i, |
| | | const std::string & file = __FILE__, |
| | | int line = __LINE__) { |
| | | std::vector<Value> arguments; |
| | | |
| | | // check the arguments types |
| | | if (tokens[i].type != TokenType::StringDeclaration && tokens[i].type != TokenType::BooleanDeclaration && |
| | | tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "variable declaration", file, line); |
| | | } |
| | | const auto parameter_type = getVariableTypeFromTokenTypeDeclaration(tokens[i].type); |
| | | if (parameter_type == Variables::Type::VT_NOT_DEFINED) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line); |
| | | } |
| | | |
| | | if (parameter_type == Variables::Type::VT_FUNCTION) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line); |
| | | } |
| | | |
| | | if (parameter_type == Variables::Type::VT_NULL) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line); |
| | | } |
| | | |
| | | Value val; |
| | | val.type = parameter_type; |
| | | val.token = tokens[i]; |
| | | |
| | | arguments.emplace_back(std::move(val)); |
| | | i++; // Skip variable declaration |
| | | |
| | | if (tokens[i].type != TokenType::RightParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], ") - Only one argument is allowed", file, line); |
| | | } |
| | | i++; // Skip ')' |
| | | |
| | | return arguments; |
| | | } |
| | | |
| | | [[nodiscard]] static std::string getFunctionBody(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const size_t first_index = i; |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::LeftCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "{"); |
| | | } |
| | | i++; // Skip '{' |
| | | std::string lines; |
| | | |
| | | while (i < tokens.size() && tokens[i].type != TokenType::RightCurlyBracket) { |
| | | if (tokens[i].type == TokenType::EndOfLine) { |
| | | lines += "\n"; |
| | | i++; |
| | | continue; |
| | | } |
| | | if (tokens[i].type == TokenType::EndOfFile) { |
| | | throw std::runtime_error("Unexpected end of file"); |
| | | break; |
| | | } |
| | | lines += tokens[i].lexeme + " "; |
| | | i++; |
| | | } |
| | | |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}"); |
| | | } |
| | | return lines; |
| | | }; |
| | | |
| | | }; // namespace ScriptInterpreterHelpers |
| | | |
| | | #endif // SCRIPTINTERPRETERHELPERS_HPP |
| | |
| | | #ifndef TOKEN_HPP |
| | | #define TOKEN_HPP |
| | | #include <cstdint> |
| | | #include <iostream> |
| | | #include <ostream> |
| | | #include <stdexcept> |
| | | #include <string> |
| | | #include <unordered_map> |
| | | |
| | |
| | | enum class TokenType : std::uint8_t { |
| | | ParserOpenTag, |
| | | ParserCloseTag, |
| | | ParserIfStatement, // if |
| | | FileClose, |
| | | Identifier, |
| | | StringLiteral, |
| | | IntLiteral, |
| | | DoubleLiteral, |
| | | LeftParenthesis, |
| | | RightParenthesis, |
| | | Comma, |
| | | Semicolon, |
| | | BooleanLiteral, |
| | | LeftParenthesis, // ( |
| | | RightParenthesis, // ) |
| | | Comma, // , |
| | | Semicolon, // ; |
| | | Variable, // $variable |
| | | VariableSign, // $ variable start sign |
| | | StringDeclaration, // string $variable |
| | | IntDeclaration, // int $variable |
| | | DoubleDeclaration, // double $variable |
| | | Equals, // = jel |
| | | EndOfFile, |
| | | EndOfLine, |
| | | Comment, |
| | | Unknown |
| | | BooleanDeclaration, // bool $variable |
| | | FunctionDeclaration, // function $variable |
| | | Equals, // = |
| | | Plus, // + |
| | | Minus, // - |
| | | Multiply, // * |
| | | Divide, // / |
| | | Modulo, // % |
| | | GreaterThan, // > |
| | | LessThan, // < |
| | | GreaterThanOrEqual, // >= |
| | | LessThanOrEqual, // <= |
| | | NotEqual, // != |
| | | Equal, // == |
| | | Not, // ! |
| | | And, // && |
| | | Or, // || |
| | | LeftBracket, // [ |
| | | RightBracket, // ] |
| | | LeftCurlyBracket, // { |
| | | RightCurlyBracket, // } |
| | | EndOfFile, // \0 |
| | | EndOfLine, // \n |
| | | Comment, // # |
| | | Unknown // Unknown |
| | | }; |
| | | |
| | | const static std::unordered_map<TokenType, std::string> tokenTypeNames = { |
| | | { TokenType::ParserOpenTag, "ParserOpenTag" }, |
| | | { TokenType::ParserCloseTag, "ParserCloseTag" }, |
| | | { TokenType::ParserIfStatement, "ParserIfStatement" }, |
| | | { TokenType::FileClose, "FileClose" }, |
| | | { TokenType::Identifier, "Identifier" }, |
| | | { TokenType::StringLiteral, "StringLiteral" }, |
| | | { TokenType::IntLiteral, "IntLiteral" }, |
| | | { TokenType::DoubleLiteral, "DoubleLiteral" }, |
| | | { TokenType::BooleanLiteral, "BooleanLiteral" }, |
| | | { TokenType::LeftParenthesis, "LeftParenthesis" }, |
| | | { TokenType::RightParenthesis, "RightParenthesis" }, |
| | | { TokenType::Comma, "Comma" }, |
| | |
| | | { TokenType::StringDeclaration, "StringDeclaration" }, |
| | | { TokenType::IntDeclaration, "IntDeclaration" }, |
| | | { TokenType::DoubleDeclaration, "DoubleDeclaration" }, |
| | | { TokenType::BooleanDeclaration, "BooleanDeclaration" }, |
| | | { TokenType::FunctionDeclaration, "FunctionDeclaration" }, |
| | | { TokenType::Equals, "Equals" }, |
| | | { TokenType::Plus, "Plus" }, |
| | | { TokenType::Minus, "Minus" }, |
| | | { TokenType::Multiply, "Multiply" }, |
| | | { TokenType::Divide, "Divide" }, |
| | | { TokenType::Modulo, "Modulo" }, |
| | | { TokenType::GreaterThan, "GreaterThan" }, |
| | | { TokenType::LessThan, "LessThan" }, |
| | | { TokenType::GreaterThanOrEqual, "GreaterThanOrEqual" }, |
| | | { TokenType::LessThanOrEqual, "LessThanOrEqual" }, |
| | | { TokenType::NotEqual, "NotEqual" }, |
| | | { TokenType::Equal, "Equal" }, |
| | | { TokenType::Not, "Not" }, |
| | | { TokenType::And, "And" }, |
| | | { TokenType::Or, "Or" }, |
| | | { TokenType::LeftBracket, "LeftBracket" }, |
| | | { TokenType::RightBracket, "RightBracket" }, |
| | | { TokenType::LeftCurlyBracket, "LeftCurlyBracket" }, |
| | | { TokenType::RightCurlyBracket, "RightCurlyBracket" }, |
| | | { TokenType::EndOfFile, "EndOfFile" }, |
| | | { TokenType::EndOfLine, "EndOfLine" }, |
| | | { TokenType::Comment, "Comment" }, |
| | | { TokenType::Unknown, "Unknown" } |
| | | }; |
| | | |
| | | [[nodiscard]] static inline std::string getTokenTypeAsString(TokenType type) { |
| | | auto it = tokenTypeNames.find(type); |
| | | if (it != tokenTypeNames.end()) { |
| | | return it->second; |
| | | } |
| | | return "Unknown"; |
| | | //throw std::runtime_error("Unknown token type"); |
| | | }; |
| | | |
| | | static const std::unordered_map<TokenType, Variables::Type> tokenTypeToVariableType = { |
| | | { TokenType::StringLiteral, Variables::Type::VT_STRING }, |
| | | { TokenType::IntLiteral, Variables::Type::VT_INT }, |
| | | { TokenType::DoubleLiteral, Variables::Type::VT_DOUBLE } |
| | | { TokenType::DoubleLiteral, Variables::Type::VT_DOUBLE }, |
| | | { TokenType::BooleanLiteral, Variables::Type::VT_BOOLEAN } |
| | | }; |
| | | |
| | | static const std::unordered_map<Variables::Type, TokenType> variableTypeToTokenType = { |
| | | { Variables::Type::VT_STRING, TokenType::StringLiteral }, |
| | | { Variables::Type::VT_INT, TokenType::IntLiteral }, |
| | | { Variables::Type::VT_DOUBLE, TokenType::DoubleLiteral }, |
| | | { Variables::Type::VT_BOOLEAN, TokenType::BooleanLiteral } |
| | | }; |
| | | |
| | | [[nodiscard]] static inline Variables::Type getVariableTypeFromTokenType(TokenType type) { |
| | |
| | | return Variables::TypeToString(getVariableTypeFromTokenType(type)); |
| | | } |
| | | |
| | | [[nodiscard]] static inline TokenType getTokenTypeFromVariableType(Variables::Type type) { |
| | | auto it = variableTypeToTokenType.find(type); |
| | | if (it != variableTypeToTokenType.end()) { |
| | | return it->second; |
| | | } |
| | | return TokenType::Unknown; |
| | | }; |
| | | |
| | | [[nodiscard]] static inline TokenType getTokenTypeFromValueDeclaration(const Variables::Type & declaration) { |
| | | if (declaration == Variables::Type::VT_STRING) { |
| | | return TokenType::StringDeclaration; |
| | | } |
| | | if (declaration == Variables::Type::VT_INT) { |
| | | return TokenType::IntDeclaration; |
| | | } |
| | | if (declaration == Variables::Type::VT_DOUBLE) { |
| | | return TokenType::DoubleDeclaration; |
| | | } |
| | | if (declaration == Variables::Type::VT_BOOLEAN) { |
| | | return TokenType::BooleanDeclaration; |
| | | } |
| | | if (declaration == Variables::Type::VT_FUNCTION) { |
| | | return TokenType::FunctionDeclaration; |
| | | } |
| | | return TokenType::Unknown; |
| | | } |
| | | |
| | | [[nodiscard]] static inline Variables::Type getVariableTypeFromTokenTypeDeclaration(const TokenType & type) { |
| | | if (type == TokenType::StringDeclaration) { |
| | | return Variables::Type::VT_STRING; |
| | | } |
| | | if (type == TokenType::IntDeclaration) { |
| | | return Variables::Type::VT_INT; |
| | | } |
| | | if (type == TokenType::DoubleDeclaration) { |
| | | return Variables::Type::VT_DOUBLE; |
| | | } |
| | | if (type == TokenType::BooleanDeclaration) { |
| | | return Variables::Type::VT_BOOLEAN; |
| | | } |
| | | if (type == TokenType::FunctionDeclaration) { |
| | | return Variables::Type::VT_FUNCTION; |
| | | } |
| | | return Variables::Type::VT_NULL; |
| | | }; |
| | | |
| | | struct TokenPos { |
| | | size_t start; |
| | | size_t end; |
| | | }; |
| | | |
| | | struct Token { |
| | | TokenType type; |
| | | std::string lexeme; |
| | | std::string file; |
| | | int lineNumber; |
| | | size_t columnNumber; |
| | | TokenPos pos; |
| | | Variables::Type variableType = Variables::Type::VT_NULL; |
| | | |
| | | [[nodiscard]] std::string getTypeName() const { return tokenTypeNames.at(type); } |
| | |
| | | #include <string> |
| | | #include <variant> |
| | | |
| | | #include "Token.hpp" |
| | | #include "VariableTypes.hpp" |
| | | |
| | | class Value { |
| | | public: |
| | | Variables::Type type = Variables::Type::VT_NULL; |
| | | Variables::DataContainer data; |
| | | Token token; |
| | | |
| | | Value() : type(Variables::Type::VT_NULL) {} |
| | | |
| | | Value(Variables::Type t, double val) : type(t), data(std::move(val)) {} |
| | | //Value(Variables::Type t, double val) : type(t), data(std::move(val)) {} |
| | | |
| | | Value(Variables::Type t, int val) : type(t), data(std::move(val)) {} |
| | | //Value(Variables::Type t, int val) : type(t), data(std::move(val)) {} |
| | | |
| | | Value(Variables::Type t, const std::string & val) : type(t), data(val) {} |
| | | //Value(Variables::Type t, const std::string & val) : type(t), data(val) {} |
| | | |
| | | Value(Variables::Type t, bool val) : type(t), data(std::move(val)) {} |
| | | //Value(Variables::Type t, bool val) : type(t), data(std::move(val)) {} |
| | | |
| | | static Value fromInt(int val) { return Value(Variables::Type::VT_INT, val); } |
| | | Value(Variables::Type variable_type, const Token & token) : type(variable_type), token(token) { |
| | | if (type == Variables::Type::VT_INT) { |
| | | data = std::stoi(token.lexeme); |
| | | } else if (type == Variables::Type::VT_DOUBLE) { |
| | | data = std::stod(token.lexeme); |
| | | } else if (type == Variables::Type::VT_BOOLEAN) { |
| | | data = token.lexeme == "true"; |
| | | } else if (type == Variables::Type::VT_NULL) { |
| | | data = Variables::TypeToString(type); |
| | | } else if (type == Variables::Type::VT_NOT_DEFINED) { |
| | | data = Variables::TypeToString(type); |
| | | } else { |
| | | data = token.lexeme; |
| | | } |
| | | } |
| | | |
| | | static Value fromDouble(double val) { return Value(Variables::Type::VT_DOUBLE, val); } |
| | | //Value(const Token & token) : token(token), type(token.variableType), data(token.lexeme) {} |
| | | |
| | | static Value fromString(const std::string & val) { return { Variables::Type::VT_STRING, val }; } |
| | | //static Value fromInt(int val) { return Value(Variables::Type::VT_INT, val); } |
| | | |
| | | static Value fromBoolean(bool val) { return { Variables::Type::VT_BOOLEAN, val }; } |
| | | //static Value fromDouble(double val) { return Value(Variables::Type::VT_DOUBLE, val); } |
| | | |
| | | //static Value fromString(const std::string & val) { return { Variables::Type::VT_STRING, val }; } |
| | | |
| | | //static Value fromBoolean(bool val) { return { Variables::Type::VT_BOOLEAN, val }; } |
| | | |
| | | static Value fromInt(const Token & token) { return Value(Variables::Type::VT_INT, token); } |
| | | |
| | | static Value fromDouble(const Token & token) { return Value(Variables::Type::VT_DOUBLE, token); } |
| | | |
| | | static Value fromString(const Token & token) { return Value(Variables::Type::VT_STRING, token); } |
| | | |
| | | static Value fromBoolean(const Token & token, bool state) { |
| | | auto result = Value(Variables::Type::VT_BOOLEAN, token); |
| | | result.data = state; |
| | | return result; |
| | | } |
| | | |
| | | std::string ToString() const { return decodeEscapes(Variables::ToString(data, type)); } |
| | | |
| | | int ToInt() const { return std::get<int>(data); } |
| | | |
| | | double ToDouble() const { return std::get<double>(data); } |
| | | |
| | | bool ToBool() const { return std::get<bool>(data); } |
| | | |
| | | std::string TypeToString() const { return Variables::TypeToString(type); } |
| | | |
| | | void SetToken(const Token & token) { this->token = token; } |
| | | |
| | | const Token & GetToken() const { return token; } |
| | | |
| | | private: |
| | | Value(Variables::Type t, std::variant<int, double, std::string, bool> && val) : type(t), data(std::move(val)) {} |
| | | |
| | |
| | | |
| | | using DataContainer = std::variant<int, double, std::string, bool>; |
| | | |
| | | enum class Type : std::uint8_t { VT_INT, VT_DOUBLE, VT_STRING, VT_BOOLEAN, VT_NULL, VT_NOT_DEFINED }; |
| | | enum class Type : std::uint8_t { VT_INT, VT_DOUBLE, VT_STRING, VT_BOOLEAN, VT_NULL, VT_FUNCTION, VT_NOT_DEFINED }; |
| | | |
| | | const std::unordered_map<std::string, Type> StringToTypeMap = { |
| | | { "int", Type::VT_INT }, |
| | | { "double", Type::VT_DOUBLE }, |
| | | { "string", Type::VT_STRING }, |
| | | { "bool", Type::VT_BOOLEAN }, |
| | | { "boolean", Type::VT_BOOLEAN }, |
| | | { "null", Type::VT_NULL }, |
| | | { "function", Type::VT_FUNCTION }, |
| | | { "not_defined", Type::VT_NOT_DEFINED }, |
| | | }; |
| | | const std::unordered_map<Type, std::string> StypeToStringMap = { |
| | | { Type::VT_INT, "int" }, |
| | | { Type::VT_DOUBLE, "double" }, |
| | | { Type::VT_STRING, "string" }, |
| | | { Type::VT_BOOLEAN, "boolean" }, |
| | | { Type::VT_BOOLEAN, "bool" }, |
| | | { Type::VT_NULL, "null" }, |
| | | { Type::VT_FUNCTION, "function" }, |
| | | { Type::VT_NOT_DEFINED, "not_defined" }, |
| | | }; |
| | | |