add function parameter handling and contextes
| | |
| | | |
| | | include(cmake/AppVersion.cmake) |
| | | |
| | | set(COMMENT_CHARACTER "#") |
| | | set(IDENTIFIER_COMMENT "#") |
| | | set(PARSER_OPEN_TAG "<?void") |
| | | set(PARSER_CLOSE_TAG "?>") |
| | | set(IDENTIFIER_FUNCTION "function") |
| | | set(IDENTIFIER_VARIABLE "$") |
| | | set(IDENTIFIER_IF "if") |
| | | set(IDENTIFIER_RETURN "return") |
| | | |
| | | message(STATUS "BUILD_CLI: ${NEED_CLI}") |
| | | message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") |
| | | message(STATUS " COMMENT_CHARACTER: ${COMMENT_CHARACTER}") |
| | | message(STATUS " IDENTIFIER_COMMENT: ${IDENTIFIER_COMMENT}") |
| | | message(STATUS " IDENTIFIER_FUNCTION: ${IDENTIFIER_FUNCTION}") |
| | | message(STATUS " IDENTIFIER_VARIABLE: ${IDENTIFIER_VARIABLE}") |
| | | message(STATUS " IDENTIFIER_IF: ${IDENTIFIER_IF}") |
| | | message(STATUS " IDENTIFIER_RETURN: ${IDENTIFIER_RETURN}") |
| | | message(STATUS " PARSER_OPEN_TAG: ${PARSER_OPEN_TAG}") |
| | | message(STATUS " PARSER_CLOSE_TAG: ${PARSER_CLOSE_TAG}") |
| | | message(STATUS "APP_GIT_VERSION: ${APP_GIT_VERSION}") |
| | |
| | | ScriptInterpreter interp; |
| | | interp.registerModule("print", std::make_shared<PrintFunction>()); |
| | | interp.registerModule("sleep", std::make_shared<SleepFunction>()); |
| | | interp.executeScript(content, filename, "_default_", false); |
| | | interp.executeScript(content, filename, "DEFAULT", false); |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "Parser error: " << e.what() << "\n"; |
| | | return 1; |
| | |
| | | #else |
| | | const char EOL = '\n'; |
| | | #endif |
| | | const char COMMENT_CHARACTER = '@COMMENT_CHARACTER@'; |
| | | const static char IDENTIFIER_COMMENT = '@IDENTIFIER_COMMENT@'; |
| | | const static char * PARSER_OPEN_TAG = "@PARSER_OPEN_TAG@"; |
| | | const static char * PARSER_CLOSE_TAG = "@PARSER_CLOSE_TAG@"; |
| | | const static char IDENTIFIER_VARIABLE = '@IDENTIFIER_VARIABLE@'; |
| | | const static char * IDENTIFIER_FUNCTION = "@IDENTIFIER_FUNCTION@"; |
| | | const static char * IDENTIFIER_RETURN = "@IDENTIFIER_RETURN@"; |
| | | const static char * IDENTIFIER_IF = "@IDENTIFIER_IF@"; |
| | | 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@"; |
| | |
| | | colNumber(1), |
| | | charNumber(0) {} |
| | | |
| | | /** |
| | | * Peek at the current character without advancing the lexer's position. |
| | | * |
| | | * @return The current character, or '\0' if at the end of the source. |
| | | */ |
| | | char Lexer::peek() const { |
| | | return pos < src.size() ? src[pos] : '\0'; |
| | | } |
| | |
| | | while (isalpha(peek())) { |
| | | lexeme += advance(); |
| | | } |
| | | if (lexeme == IDENTIFIER_FUNCTION) { |
| | | return this->functionDeclarationToken(); |
| | | } |
| | | |
| | | if (lexeme == IDENTIFIER_RETURN) { |
| | | return createToken(TokenType::Return, lexeme); |
| | | } |
| | | if (lexeme == IDENTIFIER_IF) { |
| | | return createToken(TokenType::ParserIfStatement, lexeme); |
| | | } |
| | | |
| | | if (peek() == '(') { // Function call |
| | | return createToken(TokenType::FunctionCall, lexeme); |
| | | } |
| | | |
| | | auto it = Variables::StringToTypeMap.find(lexeme); |
| | | if (it != Variables::StringToTypeMap.end()) { |
| | | const auto & type = it->second; |
| | | while (isspace(peek())) { |
| | | advance(); |
| | | } |
| | | if (peek() == '$') { |
| | | |
| | | if (peek() == IDENTIFIER_VARIABLE) { |
| | | return this->variableDeclarationToken(type); |
| | | } |
| | | return createToken(TokenType::Identifier, lexeme); |
| | | } |
| | | return createToken(TokenType::Identifier, lexeme); |
| | | } |
| | | |
| | | Token Lexer::functionDeclarationToken() { |
| | | advance(); // Skip function |
| | | std::string functionName; |
| | | if (isalpha(peek()) || peek() == '_') { |
| | | functionName += advance(); |
| | | while (isalnum(peek()) || peek() == '_') { |
| | | functionName += advance(); |
| | | } |
| | | return createToken(TokenType::FunctionDeclaration, functionName); |
| | | } |
| | | return createUnknownToken("function followed by invalid character"); |
| | | } |
| | | |
| | | Token Lexer::variableDeclarationToken(Variables::Type type) { |
| | |
| | | tokens.push_back(createSingleCharToken(TokenType::EndOfLine, "\n")); |
| | | continue; |
| | | } |
| | | if (c == COMMENT_CHARACTER) { |
| | | if (c == IDENTIFIER_COMMENT) { |
| | | tokens.push_back(commentToken()); |
| | | advance(); // Skip newline after comment |
| | | continue; |
| | |
| | | tokens.push_back(createToken(TokenType::ParserCloseTag, PARSER_CLOSE_TAG)); |
| | | continue; |
| | | } |
| | | if (matchSequence("if")) { |
| | | matchAndConsume("if"); |
| | | tokens.push_back(createToken(TokenType::ParserIfStatement, "if")); |
| | | continue; |
| | | } |
| | | |
| | | switch (c) { |
| | | case 'a' ... 'z': |
| | | case 'A' ... 'Z': |
| | | tokens.push_back(keywordOrIdentifierToken()); |
| | | break; |
| | | case '$': |
| | | case IDENTIFIER_VARIABLE: |
| | | tokens.push_back(variableToken()); |
| | | break; |
| | | case '0' ... '9': |
| | |
| | | Token variableToken(); |
| | | Token commentToken(); |
| | | Token keywordOrIdentifierToken(); |
| | | Token functionDeclarationToken(); |
| | | Token variableDeclarationToken(Variables::Type type); |
| | | |
| | | // validate number types from string |
| | |
| | | |
| | | const Token & token() const { return token_; } |
| | | |
| | | static ScriptException makeUnexpectedEndOfFileError(const Token & token, const std::string & file = "", |
| | | int line = 0) { |
| | | std::string msg = "unexpected end of file"; |
| | | if (!token.lexeme.empty()) { |
| | | msg += " near '" + token.lexeme + "'"; |
| | | } |
| | | return ScriptException(ScriptErrorType::UnexpectedToken, msg, file, line, 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 + "'"; |
| | |
| | | #endif |
| | | |
| | | if (!expected.empty()) { |
| | | msg += ", expected: '" + expected + "'"; |
| | | msg += ", expected " + expected; |
| | | } |
| | | return ScriptException(ScriptErrorType::UnexpectedToken, msg, file, line, 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)); |
| | | msg.append(", type: " + getTokenTypeAsString(token.type)); |
| | | #endif |
| | | return ScriptException(ScriptErrorType::UndefinedFunction, msg, file, line, token); |
| | | } |
| | |
| | | return ScriptException(ScriptErrorType::Custom, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeFunctionArgumentCountMismatchError(const std::string & functionName, |
| | | const size_t & expected, size_t actual, |
| | | const Token & token, const std::string & file = "", |
| | | int line = 0) { |
| | | std::string msg = "invalid argument count for function '" + functionName + "', expected " + |
| | | std::to_string(expected) + ", got " + std::to_string(actual); |
| | | return ScriptException(ScriptErrorType::Custom, msg, file, line, token); |
| | | } |
| | | |
| | | static ScriptException makeFunctionBodyEmptyError(const std::string & functionName, const Token & token, |
| | | const std::string & file = "", int line = 0) { |
| | | std::string msg = "function '" + functionName + "' has no body"; |
| | | return ScriptException(ScriptErrorType::Custom, msg, file, line, token); |
| | | } |
| | | |
| | | private: |
| | | ScriptErrorType type_; |
| | | std::string file_; |
| | |
| | | // |
| | | // Purpose of macros: unified exception handling with extended error information (source file and line number) |
| | | // |
| | | #define THROW_UNEXPECTED_END_OF_FILE_ERROR(token) \ |
| | | throw ScriptException::makeUnexpectedEndOfFileError(token, __FILE__, __LINE__) |
| | | |
| | | // Invalid token type - expected different type |
| | | #define THROW_UNEXPECTED_TOKEN_ERROR(token, expected) \ |
| | |
| | | #define THROW_INVALID_FUNCTION_ARGUMENT_ERROR(functionName, argName, token) \ |
| | | throw ScriptException::makeFunctionInvalidArgumentError(functionName, argName, token, __FILE__, __LINE__) |
| | | |
| | | #define THROW_FUNCTION_ARG_COUNT_MISMATCH_ERROR(functionName, expected, actual, token) \ |
| | | throw ScriptException::makeFunctionArgumentCountMismatchError(functionName, expected, actual, token, __FILE__, \ |
| | | __LINE__) |
| | | |
| | | #define THROW_FUNCTION_BODY_EMPTY(funcName, token) \ |
| | | throw ScriptException::makeFunctionBodyEmptyError(funcName, token, __FILE__, __LINE__) |
| | | |
| | | #endif // SCRIPT_EXCEPTION_MACROS_H |
| | |
| | | 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::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], tokenTypeNames.at(TokenType::LeftParenthesis)); |
| | | } |
| | |
| | | i++; // Skip '=' |
| | | |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Variable) { |
| | | const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__); |
| | | auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__); |
| | | |
| | | if (variable.type != Variables::Type::VT_BOOLEAN) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, |
| | |
| | | [](unsigned char c) { return std::tolower(c); }); |
| | | |
| | | if (lowered == "true") { |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true); |
| | | auto value = Value::fromBoolean(tokens[i], true); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | } else if (lowered == "false") { |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true); |
| | | auto value = Value::fromBoolean(tokens[i], false); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "true or false after '='"); |
| | | } |
| | |
| | | } else if (i < tokens.size() && tokens[i].type == TokenType::IntLiteral) { |
| | | const auto test = std::stoi(tokens[i].lexeme); |
| | | if (test == 0) { |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true); |
| | | auto value = Value::fromBoolean(tokens[i], false); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | } else if (test > 0) { |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true); |
| | | auto value = Value::fromBoolean(tokens[i], false); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='"); |
| | | } |
| | |
| | | Variables::TypeToString(Variables::Type::VT_STRING), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | this->setVariable(varToken.lexeme, variable, this->contextPrefix, true); |
| | | auto value = variable; |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | i++; // Skip variable name |
| | | EXPECT_SEMICOLON(tokens, i, "after string variable declaration"); |
| | | |
| | | } else if (i < tokens.size() && tokens[i].type == TokenType::StringLiteral) { |
| | | this->setVariable(varToken.lexeme, Value::fromString(tokens[i]), this->contextPrefix, true); |
| | | auto value = Value::fromString(tokens[i]); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | i++; // Skip string literal |
| | | EXPECT_SEMICOLON(tokens, i, "after string declaration"); |
| | | } else { |
| | |
| | | if (i < tokens.size()) { |
| | | if (type == TokenType::IntDeclaration && tokens[i].type == TokenType::IntLiteral) { |
| | | try { |
| | | this->setVariable(varToken.lexeme, Value::fromInt(tokens[i]), this->contextPrefix, true); |
| | | auto value = Value::fromInt(tokens[i]); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | 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 { |
| | | this->setVariable(varToken.lexeme, Value::fromDouble(tokens[i]), this->contextPrefix, true); |
| | | auto value = Value::fromDouble(tokens[i]); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, true); |
| | | i++; // Skip double literal |
| | | } catch (const std::invalid_argument & e) { |
| | | throw std::runtime_error("Invalid double literal in declaration: " + tokens[i].lexeme); |
| | |
| | | } |
| | | |
| | | void ScriptInterpreter::handleFunctionDeclaration(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto & funcToken = tokens[i]; |
| | | |
| | | i++; // skip funct name |
| | | |
| | |
| | | } |
| | | i++; // skip '=' |
| | | |
| | | if (this->functionParameters.find(varName) != this->functionParameters.end()) { |
| | | THROW_FUNCTION_REDEFINITION_ERROR(varName, tokens[i]); |
| | | if (this->functionBodies.find(funcToken.lexeme) != this->functionBodies.end()) { |
| | | THROW_FUNCTION_REDEFINITION_ERROR(funcToken.lexeme, tokens[i]); |
| | | } |
| | | |
| | | if (i < tokens.size() && tokens[i].type != TokenType::LeftParenthesis) { |
| | |
| | | } |
| | | i++; |
| | | // parse arg definitions |
| | | |
| | | const std::string context_name = this->getContextName(funcToken.lexeme); |
| | | const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i, __FILE__, __LINE__); |
| | | std::cout << "args: " << args.size() << '\n'; |
| | | for (const auto & arg : args) { |
| | | std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << '\n'; |
| | | auto value = arg; |
| | | this->setVariable(arg.GetToken().lexeme, value, context_name, true); |
| | | } |
| | | this->functionParameters[varName].assign(args.begin(), args.end()); |
| | | |
| | | size_t start; |
| | | size_t end; |
| | | ScriptInterpreterHelpers::getFunctionBody(tokens, i, start, end); |
| | | std::cout << "Body start: " << start << " end: " << end << '\n'; |
| | | |
| | | const std::string function_body = ScriptInterpreterHelpers::extractSubstring(this->source, start, end); |
| | | this->functionBodies[varName] = function_body; |
| | | if (function_body.empty()) { |
| | | std::cout << this->source << '\n'; |
| | | THROW_FUNCTION_BODY_EMPTY(funcToken.lexeme, tokens[i - 1]); |
| | | } |
| | | this->functionBodies[funcToken.lexeme] = function_body; |
| | | // recheck the close curly brace |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}"); |
| | | } |
| | | i++; |
| | | #if DEBUG_BUILD == 1 |
| | | std::cout << "function body: \n\"" << function_body << "\"" << '\n'; |
| | | #endif |
| | | } |
| | | |
| | | void ScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) { |
| | | auto index = i; |
| | | std::string funcName = tokens[i].lexeme; |
| | | const auto & functiontoken = tokens[i]; |
| | | |
| | | index++; // skip funct name |
| | | if (index < tokens.size() && tokens[index].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[index - 1], ""); |
| | | i++; // skip funct name |
| | | if (i < tokens.size() && tokens[i].type != TokenType::LeftParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], ""); |
| | | } |
| | | |
| | | auto it = functionObjects.find(funcName); |
| | | if (it == functionObjects.end()) { |
| | | THROW_UNDEFINED_FUNCTION_ERROR(funcName, tokens[i]); |
| | | std::vector<Value> args = parseFunctionArguments(tokens, i); |
| | | |
| | | auto it1 = functionObjects.find(functiontoken.lexeme); |
| | | if (it1 != functionObjects.end()) { |
| | | it1->second->call(args); |
| | | } else { |
| | | auto it2 = functionBodies.find(functiontoken.lexeme); |
| | | if (it2 == functionBodies.end()) { |
| | | THROW_UNDEFINED_FUNCTION_ERROR(functiontoken.lexeme, tokens[i]); |
| | | } |
| | | |
| | | // it->second->validate(tokens, i, this->variables); |
| | | if (args.size() > 0) { |
| | | auto varList = this->getcontextVariables(this->getContextName(functiontoken.lexeme)); |
| | | if (varList.size() != args.size()) { |
| | | THROW_FUNCTION_ARG_COUNT_MISMATCH_ERROR(functiontoken.lexeme, varList.size(), args.size(), tokens[i]); |
| | | } |
| | | size_t counter = 0; |
| | | for (auto & var : varList) { |
| | | this->setVariable(var.first, args[counter], var.second.context, false, true); |
| | | counter++; |
| | | } |
| | | } |
| | | |
| | | std::vector<Value> args = parseFunctionArguments(tokens, index); |
| | | it->second->call(args); |
| | | i = index; |
| | | this->executeScript(it2->second, this->filename, functiontoken.lexeme, true); |
| | | } |
| | | |
| | | //i++; |
| | | |
| | | 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) { |
| | |
| | | i++; // Skip '=' |
| | | if (i < tokens.size()) { |
| | | const auto variable = this->getVariable(varToken, this->contextPrefix, __FILE__, __LINE__); |
| | | this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]), this->contextPrefix, false); |
| | | auto value = evaluateExpression(tokens[i]); |
| | | this->setVariable(varToken.lexeme, value, this->contextPrefix, false); |
| | | i++; // Skip value |
| | | EXPECT_SEMICOLON(tokens, i, "after variable assignment"); |
| | | } else { |
| | |
| | | |
| | | void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, |
| | | const std::string & _namespace, bool ignore_tags) { |
| | | std::string oldContext; |
| | | std::string oldSource; |
| | | |
| | | this->filename = filename; |
| | | if (!this->source.empty()) { |
| | | oldSource = this->source; |
| | | } |
| | | this->source = source; |
| | | this->contextPrefix = filename + _namespace; |
| | | |
| | | if (!this->contextPrefix.empty()) { |
| | | oldContext = this->contextPrefix; |
| | | } |
| | | this->contextPrefix = this->getContextName(_namespace); |
| | | Lexer lexer(source, filename); |
| | | auto tokens = lexer.tokenize(); |
| | | |
| | |
| | | case TokenType::DoubleDeclaration: |
| | | handleNumberDeclaration(tokens, i, token.type); |
| | | break; |
| | | case TokenType::Identifier: |
| | | case TokenType::FunctionCall: |
| | | handleFunctionCall(tokens, i); |
| | | break; |
| | | case TokenType::Variable: |
| | |
| | | THROW_UNEXPECTED_TOKEN_ERROR(token, ""); |
| | | } |
| | | } |
| | | if (!oldContext.empty()) { |
| | | this->contextPrefix = oldContext; |
| | | oldContext.clear(); |
| | | } |
| | | if (!oldSource.empty()) { |
| | | this->source = oldSource; |
| | | oldSource.clear(); |
| | | } |
| | | } |
| | |
| | | #ifndef SSCRIPTINTERPRETER_HPP |
| | | #define SSCRIPTINTERPRETER_HPP |
| | | #include <functional> |
| | | #include <map> |
| | | #include <memory> |
| | | #include <string> |
| | | #include <unordered_map> |
| | |
| | | |
| | | 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>; |
| | | using VariableContext = std::map<std::string, Value>; |
| | | |
| | | class ScriptInterpreter { |
| | | public: |
| | | void registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn); |
| | | void executeScript(const std::string & source, const std::string & filename, |
| | | const std::string & _namespace = "_default_", bool ignore_tags = false); |
| | | const std::string & _namespace = "DEFAULT", bool ignore_tags = false); |
| | | |
| | | private: |
| | | std::unordered_map<std::string, FunctionValidator> functionValidators; |
| | |
| | | [[nodiscard]] Value evaluateExpression(const Token & token) const; |
| | | |
| | | // type handlers |
| | | void setVariable(const std::string & name, const Value & value, const std::string & context = "default", |
| | | bool exception_if_exists = false) { |
| | | void setVariable(const std::string & name, Value & value, const std::string & context = "default", |
| | | bool exception_if_exists = false, bool exception_if_not_exists = false) { |
| | | if (exception_if_exists && variables[context].find(name) != variables[context].end()) { |
| | | THROW_VARIABLE_REDEFINITION_ERROR(name, value.token); |
| | | } |
| | | if (exception_if_not_exists && variables[context].find(name) == variables[context].end()) { |
| | | THROW_UNDEFINED_VARIABLE_ERROR(name, value.token); |
| | | } |
| | | value.name = name; |
| | | value.context = context; |
| | | this->variables[context][name] = value; |
| | | } |
| | | |
| | |
| | | } |
| | | THROW_UNDEFINED_VARIABLE_ERROR_HELPER(token.lexeme, token, file, line); |
| | | }; |
| | | |
| | | [[nodiscard]] std::map<std::string, Value> getcontextVariables(const std::string & context) const { |
| | | auto it = variables.find(context); |
| | | if (it != variables.end()) { |
| | | return it->second; |
| | | } |
| | | throw std::runtime_error("Context not found: " + context); |
| | | } |
| | | |
| | | /** |
| | | * Checks if a variable exists within the specified context. |
| | |
| | | |
| | | void handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i); |
| | | void handleVariableReference(const std::vector<Token> & tokens, std::size_t & i); |
| | | |
| | | static void handleComment(std::size_t & i){ i++;} |
| | | |
| | | static void handleSemicolon(std::size_t & i) {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); |
| | | |
| | | std::string getContextName(const std::string & suffix) const { return this->filename + "::" + suffix; } |
| | | }; |
| | | |
| | | #endif // SSCRIPTINTERPRETER_HPP |
| | |
| | | if (tokens[i].type != TokenType::StringDeclaration && tokens[i].type != TokenType::BooleanDeclaration && |
| | | tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration && |
| | | tokens[i].type != TokenType::RightParenthesis) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "variable declaration", file, line); |
| | | THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "variable declaration: 'type $" + tokens[i].lexeme + "'", file, |
| | | line); |
| | | } |
| | | if (tokens[i].type != TokenType::RightParenthesis) { |
| | | const auto parameter_type = getVariableTypeFromTokenTypeDeclaration(tokens[i].type); |
| | |
| | | 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_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); |
| | |
| | | static void getFunctionBody(const std::vector<Token> & tokens, std::size_t & i, std::size_t & start, |
| | | std::size_t & end) { |
| | | start = tokens[i].pos.end; |
| | | std::cout << "START Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start) |
| | | << " end pos: " << std::to_string(tokens[i].pos.end) << '\n'; |
| | | |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::LeftCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "{"); |
| | | } |
| | |
| | | continue; |
| | | } |
| | | if (tokens[i].type == TokenType::EndOfFile) { |
| | | throw std::runtime_error("Unexpected end of file"); |
| | | THROW_UNEXPECTED_END_OF_FILE_ERROR(tokens[i]); |
| | | break; |
| | | } |
| | | i++; |
| | | } |
| | | end = tokens[i].pos.start - 1; |
| | | |
| | | std::cout << "END Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start) |
| | | << " end pos: " << std::to_string(tokens[i].pos.end) << '\n'; |
| | | |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}"); |
| | |
| | | #ifndef TOKEN_HPP |
| | | #define TOKEN_HPP |
| | | #include <cstdint> |
| | | #include <iostream> |
| | | #include <string> |
| | | #include <unordered_map> |
| | | |
| | |
| | | IntDeclaration, // int $variable |
| | | DoubleDeclaration, // double $variable |
| | | BooleanDeclaration, // bool $variable |
| | | FunctionDeclaration, // function $variable |
| | | FunctionDeclaration, // function fn_name |
| | | FunctionCall, // fn_name(args) |
| | | Return, // return |
| | | Equals, // = |
| | | Plus, // + |
| | | Minus, // - |
| | |
| | | { TokenType::DoubleDeclaration, "DoubleDeclaration" }, |
| | | { TokenType::BooleanDeclaration, "BooleanDeclaration" }, |
| | | { TokenType::FunctionDeclaration, "FunctionDeclaration" }, |
| | | { TokenType::FunctionCall, "FunctionCall" }, |
| | | { TokenType::Return, "Return" }, |
| | | { TokenType::Equals, "Equals" }, |
| | | { TokenType::Plus, "Plus" }, |
| | | { TokenType::Minus, "Minus" }, |
| | |
| | | if (declaration == Variables::Type::VT_BOOLEAN) { |
| | | return TokenType::BooleanDeclaration; |
| | | } |
| | | if (declaration == Variables::Type::VT_FUNCTION) { |
| | | return TokenType::FunctionDeclaration; |
| | | } |
| | | // if (declaration == Variables::Type::VT_FUNCTION) { |
| | | // return TokenType::FunctionDeclaration; |
| | | // } |
| | | std::cout << "Unknown variable type: " << Variables::TypeToString(declaration) << "\n"; |
| | | return TokenType::Unknown; |
| | | } |
| | | |
| | |
| | | if (type == TokenType::BooleanDeclaration) { |
| | | return Variables::Type::VT_BOOLEAN; |
| | | } |
| | | if (type == TokenType::FunctionDeclaration) { |
| | | return Variables::Type::VT_FUNCTION; |
| | | } |
| | | //if (type == TokenType::FunctionDeclaration) { |
| | | // return Variables::Type::VT_FUNCTION; |
| | | //} |
| | | return Variables::Type::VT_NULL; |
| | | }; |
| | | |
| | |
| | | Variables::Type type = Variables::Type::VT_NULL; |
| | | Variables::DataContainer data; |
| | | Token token; |
| | | std::string name; |
| | | std::string context; |
| | | |
| | | Value() : type(Variables::Type::VT_NULL) {} |
| | | |
| | |
| | | |
| | | 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_FUNCTION, VT_NOT_DEFINED }; |
| | | enum class Type : std::uint8_t { VT_INT, VT_DOUBLE, VT_STRING, VT_BOOLEAN, VT_NULL, VT_NOT_DEFINED }; |
| | | |
| | | const std::unordered_map<std::string, Type> StringToTypeMap = { |
| | | { "int", Type::VT_INT }, |
| | |
| | | { "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_STRING, "string" }, |
| | | { Type::VT_BOOLEAN, "bool" }, |
| | | { Type::VT_NULL, "null" }, |
| | | { Type::VT_FUNCTION, "function" }, |
| | | { Type::VT_NOT_DEFINED, "not_defined" }, |
| | | }; |
| | | |