#include "ScriptInterpreter.hpp" #include #include #include #include #include "Lexer.hpp" #include "ScriptExceptionMacros.h" #include "Value.hpp" void ScriptInterpreter::registerModule(const std::string & name, std::shared_ptr fn) { functionObjects[name] = std::move(fn); } Value ScriptInterpreter::evaluateExpression(const Token & token) const { if (token.type == TokenType::StringLiteral) { return Value::fromString(token); } if (token.type == TokenType::IntLiteral) { try { 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) { throw std::runtime_error("Integer literal out of range: " + token.lexeme); } } if (token.type == TokenType::DoubleLiteral) { try { 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) { throw std::runtime_error("Double literal out of range: " + token.lexeme); } } if (token.type == TokenType::Variable) { return this->getVariable(token); } THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable"); return Value(); } std::vector ScriptInterpreter::parseFunctionArguments(const std::vector & tokens, std::size_t & index) const { std::vector 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)); } current_index++; // Skip '(' while (current_index < tokens.size() && tokens[current_index].type != TokenType::RightParenthesis) { args.push_back(evaluateExpression(tokens[current_index])); current_index++; if (current_index < tokens.size() && tokens[current_index].type == TokenType::Comma) { current_index++; // Skip ',' if (current_index >= tokens.size() || tokens[current_index].type == TokenType::RightParenthesis) { THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], "expression after comma"); } } else if (tokens[current_index].type != TokenType::RightParenthesis && current_index < tokens.size()) { THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], "',' or ')'"); } } if (current_index >= tokens.size() || tokens[current_index].type != TokenType::RightParenthesis) { THROW_UNEXPECTED_TOKEN_ERROR(tokens[current_index], "')'"); } current_index++; // Skip ')' index = current_index; return args; } void ScriptInterpreter::handleBooleanDeclaration(const std::vector & 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 & 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_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) { this->setVariable(varName, Value::fromString(tokens[i])); i++; // Skip string literal EXPECT_SEMICOLON(tokens, i, "after string declaration"); } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "string literal after '='"); } } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after string declaration"); } } void ScriptInterpreter::handleNumberDeclaration(const std::vector & tokens, std::size_t & i, TokenType type) { 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()) { if (type == TokenType::IntDeclaration && tokens[i].type == TokenType::IntLiteral) { try { 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]); } 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); } catch (const std::out_of_range & e) { throw std::runtime_error("Integer literal out of range in declaration: " + tokens[i].lexeme); } } else if (type == TokenType::DoubleDeclaration && tokens[i].type == TokenType::DoubleLiteral) { try { 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]); } 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); } catch (const std::out_of_range & e) { throw std::runtime_error("Double literal out of range in declaration: " + tokens[i].lexeme); } } else { const std::string expectedType = type == TokenType::IntDeclaration ? "int" : "double"; THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, expectedType, "", getVariableTypeFromTokenTypeAsString(tokens[i].type), tokens[i]); } EXPECT_SEMICOLON(tokens, i, "after variable declaration"); } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "literal after '='"); } } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varName); } } void ScriptInterpreter::handleFunctionDeclaration(const std::vector & 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(); //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 & 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_UNDEFINED_FUNCTION_ERROR(funcName, tokens[i]); } // it->second->validate(tokens, i, this->variables); std::vector args = parseFunctionArguments(tokens, index); it->second->call(args); 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 & 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& 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()) { const auto variable = this->getVariable(varToken); this->setVariable(varToken.lexeme, evaluateExpression(tokens[i])); i++; // Skip value EXPECT_SEMICOLON(tokens, i, "after variable assignment"); } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "value after '='"); } } else { THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "'=' for assignment"); } } void ScriptInterpreter::handleComment(std::size_t & i) { i++; // Skip comment token } void ScriptInterpreter::handleSemicolon(std::size_t & i) { i++; // Skip semicolon token } 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(); bool insideScript = false; for (std::size_t i = 0; i < tokens.size();) { const auto & token = tokens[i]; if (token.type == TokenType::EndOfFile) { break; } if (token.type == TokenType::ParserOpenTag) { insideScript = true; i++; // Skip the open tag continue; } if (token.type == TokenType::ParserCloseTag) { insideScript = false; i++; // Skip the close tag continue; } if (insideScript == false && ignore_tags == false) { //std::cout << token.lexeme; i++; continue; } 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: handleNumberDeclaration(tokens, i, token.type); break; case TokenType::Identifier: handleFunctionCall(tokens, i); break; case TokenType::Variable: handleVariableReference(tokens, i); break; case TokenType::Comment: handleComment(i); break; case TokenType::Semicolon: handleSemicolon(i); break; default: THROW_UNEXPECTED_TOKEN_ERROR(token, ""); } } }