implement variable contexts, add function body store and parsing
8 files modified
1 files added
| New file |
| | |
| | | CompileFlags: |
| | | Add: [-Wunused] |
| | |
| | | if (CMAKE_BUILD_TYPE STREQUAL "Debug") |
| | | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") |
| | | set(DEBUG_BUILD ON) |
| | | set(COMPILE_WARNING_AS_ERROR ON) |
| | | endif() |
| | | |
| | | configure_file("cmake/options.h.in" "include/options.h" @ONLY) |
| | |
| | | ScriptInterpreter interp; |
| | | interp.registerModule("print", std::make_shared<PrintFunction>()); |
| | | interp.registerModule("sleep", std::make_shared<SleepFunction>()); |
| | | interp.executeScript(content, filename, DEBUG); |
| | | interp.executeScript(content, filename, "_default_", false); |
| | | } catch (const std::exception & e) { |
| | | std::cerr << "Parser error: " << e.what() << "\n"; |
| | | return 1; |
| | |
| | | return pos >= src.size(); |
| | | } |
| | | |
| | | Token Lexer::string() { |
| | | Token Lexer::createToken(TokenType type, const std::string & lexeme) const { |
| | | size_t startChar = charNumber - lexeme.length(); |
| | | return { |
| | | type, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::createSingleCharToken(TokenType type, const std::string & lexeme) { |
| | | size_t startCol = colNumber; |
| | | size_t startChar = charNumber; |
| | | advance(); |
| | | return { |
| | | type, lexeme, filename, lineNumber, startCol, { startChar, charNumber } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::createUnknownToken(const std::string & lexeme) const { |
| | | size_t startChar = charNumber - lexeme.length(); |
| | | return { |
| | | TokenType::Unknown, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::stringToken() { |
| | | std::string result; |
| | | size_t startChar = charNumber; |
| | | size_t startCol = colNumber; |
| | | advance(); // Skip opening quote |
| | | while (!isAtEnd() && peek() != '"') { |
| | | result += advance(); |
| | | } |
| | | if (isAtEnd() || peek() != '"') { |
| | | return { TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | advance(); // Skip closing quote |
| | | return { TokenType::StringLiteral, result, filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::StringLiteral, result, filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::number() { |
| | | Token Lexer::numberToken() { |
| | | std::string result; |
| | | std::string found; |
| | | TokenType type = TokenType::Unknown; |
| | | bool decimalPointSeen = false; |
| | | size_t startChar = charNumber; |
| | | size_t startCol = colNumber; |
| | | |
| | | while (std::isdigit(peek()) || peek() == '.') { |
| | | if (peek() == '.') { |
| | | if (decimalPointSeen) { |
| | | return { TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | decimalPointSeen = true; |
| | | } |
| | |
| | | result = found; |
| | | type = TokenType::IntLiteral; |
| | | } else { |
| | | return { TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | } else { |
| | | if (is_number<double>(found)) { |
| | | result = found; |
| | | type = TokenType::DoubleLiteral; |
| | | } else { |
| | | return { TokenType::Unknown, "Invalid double", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "Invalid double", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | } |
| | | } else { |
| | | return { TokenType::Unknown, "Expected number", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "Expected number", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | return { type, result, filename, lineNumber, startCol }; |
| | | return { |
| | | type, result, filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::identifier() { |
| | | Token Lexer::identifierToken() { |
| | | std::string result; |
| | | size_t startChar = charNumber; |
| | | size_t startCol = colNumber; |
| | | while (isalnum(peek()) || peek() == '_') { |
| | | result += advance(); |
| | | } |
| | | return { TokenType::Identifier, result, filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Identifier, result, filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::variable() { |
| | | Token Lexer::variableToken() { |
| | | size_t startChar = charNumber; |
| | | size_t startCol = colNumber; |
| | | advance(); // Skip $ |
| | | std::string varName; |
| | |
| | | while (isalnum(peek()) || peek() == '_') { |
| | | varName += advance(); |
| | | } |
| | | return { TokenType::Variable, varName, filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Variable, varName, filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | return { TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::comment() { |
| | | Token Lexer::commentToken() { |
| | | size_t startChar = charNumber; |
| | | size_t startCol = colNumber; |
| | | advance(); // Skip # |
| | | std::string commentText; |
| | | while (!isAtEnd() && peek() != '\n') { |
| | | commentText += advance(); |
| | | } |
| | | return { TokenType::Comment, commentText, filename, lineNumber, startCol }; |
| | | return { |
| | | TokenType::Comment, commentText, filename, lineNumber, startCol, { startChar, pos } |
| | | }; |
| | | } |
| | | |
| | | Token Lexer::keywordOrIdentifier() { |
| | | Token Lexer::keywordOrIdentifierToken() { |
| | | std::string lexeme; |
| | | size_t startCol = colNumber; |
| | | while (isalpha(peek())) { |
| | | lexeme += advance(); |
| | | } |
| | |
| | | advance(); |
| | | } |
| | | if (peek() == '$') { |
| | | return this->variableDeclaration(type); |
| | | return this->variableDeclarationToken(type); |
| | | } |
| | | return { TokenType::Identifier, lexeme, filename, lineNumber, startCol }; |
| | | return createToken(TokenType::Identifier, lexeme); |
| | | } |
| | | return createToken(TokenType::Identifier, lexeme); |
| | | } |
| | | |
| | | return { TokenType::Identifier, lexeme, filename, lineNumber, startCol }; |
| | | } |
| | | |
| | | Token Lexer::variableDeclaration(Variables::Type type) { |
| | | size_t startCol = colNumber; |
| | | Token Lexer::variableDeclarationToken(Variables::Type type) { |
| | | advance(); // Skip $ |
| | | std::string varName; |
| | | if (isalpha(peek()) || peek() == '_') { |
| | |
| | | } |
| | | for (auto it = Variables::StringToTypeMap.begin(); it != Variables::StringToTypeMap.end(); ++it) { |
| | | if (it->second == type) { |
| | | return { getTokenTypeFromValueDeclaration(it->second), varName, filename, lineNumber, startCol }; |
| | | return createToken(getTokenTypeFromValueDeclaration(it->second), varName); |
| | | } |
| | | } |
| | | |
| | | return { TokenType::Unknown, "Invalid variable type in declaration", filename, lineNumber, startCol }; |
| | | return createUnknownToken("Invalid variable type in declaration"); |
| | | } |
| | | return { TokenType::Unknown, "$ followed by invalid character in declaration", filename, lineNumber, startCol }; |
| | | } |
| | | |
| | | Token Lexer::singleCharToken(TokenType type, const std::string & lexeme) { |
| | | size_t startCol = colNumber; |
| | | advance(); |
| | | return { type, lexeme, filename, lineNumber, startCol }; |
| | | return createUnknownToken("$ followed by invalid character in declaration"); |
| | | } |
| | | |
| | | bool Lexer::matchSequence(const std::string & sequence, bool caseSensitive) const { |
| | | if (this->pos + sequence.size() > src.size()) { |
| | | return false; |
| | | } |
| | | |
| | | 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)); |
| | | } |
| | | |
| | | if (srcChar != seqChar) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | |
| | | |
| | | while (pos < src.size()) { |
| | | char c = src[pos]; |
| | | |
| | | if (isspace(c)) { |
| | | advance(); |
| | | continue; |
| | | } |
| | | if (c == '\n') { |
| | | tokens.push_back(singleCharToken(TokenType::EndOfLine, "\n")); |
| | | tokens.push_back(createSingleCharToken(TokenType::EndOfLine, "\n")); |
| | | continue; |
| | | } |
| | | if (c == COMMENT_CHARACTER) { |
| | | tokens.push_back(comment()); |
| | | tokens.push_back(commentToken()); |
| | | advance(); // Skip newline after comment |
| | | continue; |
| | | } |
| | | if (matchSequence(PARSER_OPEN_TAG)) { |
| | | size_t startCol = colNumber; |
| | | matchAndConsume(PARSER_OPEN_TAG); |
| | | tokens.push_back({ TokenType::ParserOpenTag, PARSER_OPEN_TAG, filename, lineNumber, startCol }); |
| | | tokens.push_back(createToken(TokenType::ParserOpenTag, PARSER_OPEN_TAG)); |
| | | continue; |
| | | } |
| | | if (matchSequence(PARSER_CLOSE_TAG)) { |
| | | size_t startCol = colNumber; |
| | | matchAndConsume(PARSER_CLOSE_TAG); |
| | | tokens.push_back({ TokenType::ParserCloseTag, PARSER_CLOSE_TAG, filename, lineNumber, startCol }); |
| | | tokens.push_back(createToken(TokenType::ParserCloseTag, PARSER_CLOSE_TAG)); |
| | | continue; |
| | | } |
| | | if (matchSequence("if")) { |
| | | size_t startCol = colNumber; |
| | | matchAndConsume("if"); |
| | | tokens.push_back({ TokenType::ParserIfStatement, "if", filename, lineNumber, startCol }); |
| | | tokens.push_back(createToken(TokenType::ParserIfStatement, "if")); |
| | | continue; |
| | | } |
| | | |
| | | switch (c) { |
| | | case 'a' ... 'z': |
| | | case 'A' ... 'Z': |
| | | tokens.push_back(keywordOrIdentifier()); |
| | | tokens.push_back(keywordOrIdentifierToken()); |
| | | break; |
| | | case '$': |
| | | tokens.push_back(variable()); |
| | | tokens.push_back(variableToken()); |
| | | break; |
| | | case '0' ... '9': |
| | | tokens.push_back(number()); |
| | | tokens.push_back(numberToken()); |
| | | break; |
| | | case '"': |
| | | case '\'': |
| | | tokens.push_back(string()); |
| | | tokens.push_back(stringToken()); |
| | | break; |
| | | case '(': |
| | | tokens.push_back(singleCharToken(TokenType::LeftParenthesis, "(")); |
| | | tokens.push_back(createSingleCharToken(TokenType::LeftParenthesis, "(")); |
| | | break; |
| | | case ')': |
| | | tokens.push_back(singleCharToken(TokenType::RightParenthesis, ")")); |
| | | tokens.push_back(createSingleCharToken(TokenType::RightParenthesis, ")")); |
| | | break; |
| | | case ',': |
| | | tokens.push_back(singleCharToken(TokenType::Comma, ",")); |
| | | tokens.push_back(createSingleCharToken(TokenType::Comma, ",")); |
| | | break; |
| | | case ';': |
| | | tokens.push_back(singleCharToken(TokenType::Semicolon, ";")); |
| | | tokens.push_back(createSingleCharToken(TokenType::Semicolon, ";")); |
| | | break; |
| | | case '=': |
| | | tokens.push_back(singleCharToken(TokenType::Equals, "=")); |
| | | tokens.push_back(createSingleCharToken(TokenType::Equals, "=")); |
| | | break; |
| | | case '+': |
| | | tokens.push_back(singleCharToken(TokenType::Plus, "+")); |
| | | tokens.push_back(createSingleCharToken(TokenType::Plus, "+")); |
| | | break; |
| | | case '{': |
| | | tokens.push_back(singleCharToken(TokenType::LeftCurlyBracket, "{")); |
| | | tokens.push_back(createSingleCharToken(TokenType::LeftCurlyBracket, "{")); |
| | | break; |
| | | case '}': |
| | | tokens.push_back(singleCharToken(TokenType::RightCurlyBracket, "}")); |
| | | tokens.push_back(createSingleCharToken(TokenType::RightCurlyBracket, "}")); |
| | | break; |
| | | default: |
| | | tokens.push_back({ TokenType::Unknown, std::string(1, c), filename, lineNumber, colNumber }); |
| | | tokens.push_back(createUnknownToken(std::string(1, c))); |
| | | advance(); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | tokens.push_back({ TokenType::EndOfFile, "", filename, lineNumber, colNumber }); |
| | | tokens.push_back({ |
| | | TokenType::EndOfFile, "", filename, lineNumber, colNumber, { charNumber, charNumber } |
| | | }); |
| | | return tokens; |
| | | } |
| | |
| | | Token variableDeclaration(Variables::Type type); |
| | | void matchAndConsume(const std::string & sequence, bool caseSensitive = true); |
| | | |
| | | // create token methods |
| | | Token createToken(TokenType type, const std::string & lexeme) const; |
| | | Token createSingleCharToken(TokenType type, const std::string & lexeme); |
| | | Token createUnknownToken(const std::string & lexeme) const; |
| | | Token createErrorToken(const std::string & lexeme) const; |
| | | Token stringToken(); |
| | | Token numberToken(); |
| | | Token identifierToken(); |
| | | Token variableToken(); |
| | | Token commentToken(); |
| | | Token keywordOrIdentifierToken(); |
| | | Token variableDeclarationToken(Variables::Type type); |
| | | |
| | | // validate number types from string |
| | | template <typename Numeric> static bool is_number(const std::string & s) { |
| | | Numeric n; |
| | |
| | | #define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \ |
| | | throw ScriptException::makeUndefinedVariableError(name, token, __FILE__, __LINE__) |
| | | |
| | | #define THROW_UNDEFINED_VARIABLE_ERROR_HELPER(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__) |
| | |
| | | throw std::runtime_error("Double literal out of range: " + token.lexeme); |
| | | } |
| | | } |
| | | if (token.type == TokenType::BooleanLiteral || token.type == TokenType::Identifier) { |
| | | std::string lowered = token.lexeme; |
| | | std::transform(lowered.begin(), lowered.end(), lowered.begin(), |
| | | [](unsigned char c) { return std::tolower(c); }); |
| | | return Value::fromBoolean(token, (lowered == "true" ? true : false)); |
| | | } |
| | | if (token.type == TokenType::Variable) { |
| | | return this->getVariable(token); |
| | | return this->getVariable(token, this->contextPrefix, __FILE__, __LINE__); |
| | | } |
| | | THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable"); |
| | | |
| | |
| | | } |
| | | |
| | | void ScriptInterpreter::handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto varType = tokens[i].variableType; |
| | | const auto & varToken = tokens[i]; |
| | | 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]); |
| | | const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__); |
| | | |
| | | if (variable.type != Variables::Type::VT_BOOLEAN) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_BOOLEAN), |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, |
| | | Variables::TypeToString(Variables::Type::VT_BOOLEAN), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | this->setVariable(varName, variable); |
| | | this->setVariable(varToken.lexeme, variable, this->contextPrefix, true); |
| | | i++; // Skip variable name |
| | | EXPECT_SEMICOLON(tokens, i, "after bool variable declaration"); |
| | | |
| | | } else if (i < tokens.size() && tokens[i].type == TokenType::Identifier) { |
| | | } else if (i < tokens.size() && |
| | | (tokens[i].type == TokenType::Identifier || tokens[i].type == TokenType::StringLiteral)) { |
| | | 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)); |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true); |
| | | } else if (lowered == "false") { |
| | | this->setVariable(varName, Value::fromBoolean(tokens[i], false)); |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "true or false after '='"); |
| | | } |
| | | i++; // Skip boolean literal |
| | | EXPECT_SEMICOLON(tokens, i, "after bool declaration"); |
| | | } 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); |
| | | } else if (test > 0) { |
| | | this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true); |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='"); |
| | | } |
| | | i++; |
| | | 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; |
| | | const auto varType = tokens[i].variableType; |
| | | const auto varToken = tokens[i]; |
| | | |
| | | 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]); |
| | | const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__); |
| | | |
| | | if (variable.type != Variables::Type::VT_STRING) { |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING), |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, |
| | | Variables::TypeToString(Variables::Type::VT_STRING), |
| | | tokens[i].lexeme, variable.TypeToString(), tokens[i]); |
| | | } |
| | | this->setVariable(varName, variable); |
| | | //variables[varName] = variables[tokens[i].lexeme]; |
| | | this->setVariable(varToken.lexeme, variable, 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(varName, Value::fromString(tokens[i])); |
| | | this->setVariable(varToken.lexeme, Value::fromString(tokens[i]), this->contextPrefix, true); |
| | | i++; // Skip string literal |
| | | EXPECT_SEMICOLON(tokens, i, "after string declaration"); |
| | | } else { |
| | |
| | | } |
| | | |
| | | void ScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type) { |
| | | const auto varName = tokens[i].lexeme; |
| | | const auto varType = tokens[i].variableType; |
| | | const auto & varToken = tokens[i]; |
| | | |
| | | i++; // Skip variable name |
| | | if (i < tokens.size() && tokens[i].type == TokenType::Equals) { |
| | |
| | | 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])); |
| | | this->setVariable(varToken.lexeme, Value::fromInt(tokens[i]), 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 { |
| | | 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])); |
| | | this->setVariable(varToken.lexeme, Value::fromDouble(tokens[i]), 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); |
| | |
| | | } |
| | | } else { |
| | | const std::string expectedType = type == TokenType::IntDeclaration ? "int" : "double"; |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, expectedType, "", |
| | | THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, expectedType, "", |
| | | getVariableTypeFromTokenTypeAsString(tokens[i].type), tokens[i]); |
| | | } |
| | | EXPECT_SEMICOLON(tokens, i, "after variable declaration"); |
| | |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "literal after '='"); |
| | | } |
| | | } else { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varName); |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varToken.lexeme); |
| | | } |
| | | } |
| | | |
| | |
| | | i++; |
| | | // parse arg definitions |
| | | |
| | | const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i); |
| | | const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i, __FILE__, __LINE__); |
| | | 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; |
| | | size_t start; |
| | | size_t end; |
| | | ScriptInterpreterHelpers::getFunctionBody(tokens, i, start, end); |
| | | std::cout << "Body start: " << start << " end: " << end << std::endl; |
| | | const std::string function_body = ScriptInterpreterHelpers::extractSubstring(this->source, start, end); |
| | | this->functionBodies[varName] = function_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; |
| | | #if DEBUG_BUILD == 1 |
| | | std::cout << "function body: \n\"" << function_body << "\"" << std::endl; |
| | | #endif |
| | | } |
| | | |
| | | void ScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) { |
| | |
| | | } |
| | | |
| | | 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& 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])); |
| | | const auto variable = this->getVariable(varToken, this->contextPrefix, __FILE__, __LINE__); |
| | | this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]), this->contextPrefix, false); |
| | | i++; // Skip value |
| | | EXPECT_SEMICOLON(tokens, i, "after variable assignment"); |
| | | } else { |
| | |
| | | i++; // Skip semicolon token |
| | | } |
| | | |
| | | void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool ignore_tags) { |
| | | void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, |
| | | const std::string & _namespace, bool ignore_tags) { |
| | | this->filename = filename; |
| | | this->source = source; |
| | | this->contextPrefix = filename + _namespace; |
| | | Lexer lexer(source, filename); |
| | | auto tokens = lexer.tokenize(); |
| | | |
| | |
| | | #ifndef SSCRIPTINTERPRETER_HPP |
| | | #define SSCRIPTINTERPRETER_HPP |
| | | #include <functional> |
| | | #include <map> |
| | | #include <memory> |
| | | #include <string> |
| | | #include <unordered_map> |
| | |
| | | class ScriptInterpreter { |
| | | public: |
| | | 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); |
| | | void executeScript(const std::string & source, const std::string & filename, |
| | | const std::string & _namespace = "_default_", bool ignore_tags = false); |
| | | |
| | | private: |
| | | std::unordered_map<std::string, FunctionValidator> functionValidators; |
| | |
| | | std::unordered_map<std::string, std::vector<Value>> functionParameters; |
| | | std::unordered_map<std::string, std::string> functionBodies; |
| | | std::string filename; |
| | | std::string source; |
| | | std::string contextPrefix; |
| | | |
| | | [[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 setVariable(const std::string & name, const Value & value, const std::string & context = "default") { |
| | | void setVariable(const std::string & name, const Value & value, const std::string & context = "default", |
| | | bool exception_if_exists = false) { |
| | | if (exception_if_exists && variables[context].find(name) != variables[context].end()) { |
| | | THROW_VARIABLE_REDEFINITION_ERROR(name, value.token); |
| | | } |
| | | this->variables[context][name] = value; |
| | | } |
| | | |
| | |
| | | throw std::runtime_error("Variable not found: " + name); |
| | | }; |
| | | |
| | | [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default") const { |
| | | [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default", |
| | | const std::string & file = __FILE__, const int & line = __LINE__) const { |
| | | for (auto it = variables.begin(); it != variables.end(); ++it) { |
| | | if (it->first == context) { |
| | | const auto & innerMap = it->second.find(token.lexeme); |
| | |
| | | } |
| | | } |
| | | } |
| | | THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token); |
| | | THROW_UNDEFINED_VARIABLE_ERROR_HELPER(token.lexeme, token, file, line); |
| | | }; |
| | | |
| | | /** |
| | | * Checks if a variable exists within the specified context. |
| | | * |
| | | * @param name The name of the variable to check. |
| | | * @param context The context in which to search for the variable (defaults to "default"). |
| | | * @param file The source file where the check is performed (for error reporting). |
| | | * @param line The line number where the check is performed (for error reporting). |
| | | * @return True if the variable exists in the specified context. |
| | | * @throws Throws an undefined variable error if the variable is not found. |
| | | */ |
| | | [[nodiscard]] bool variableExists(const std::string & name, const std::string & context = "default", |
| | | const std::string & file = __FILE__, const int & line = __LINE__) 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 true; |
| | | } |
| | | } |
| | | } |
| | | THROW_UNDEFINED_VARIABLE_ERROR_HELPER(name, Token(TokenType::Variable, name, name, 0, 0), file, line); |
| | | } |
| | | |
| | | /** |
| | | * Checks if a variable exists within the specified context using a Token. |
| | | * |
| | | * @param token The token representing the variable to check. |
| | | * @param context The context in which to search for the variable (defaults to "default"). |
| | | * @param file The source file where the check is performed (for error reporting). |
| | | * @param line The line number where the check is performed (for error reporting). |
| | | * @return True if the variable exists in the specified context. |
| | | * @throws Throws an undefined variable error if the variable is not found. |
| | | */ |
| | | [[nodiscard]] bool variableExists(const Token & token, const std::string & context = "default", |
| | | const std::string & file = __FILE__, const int & line = __LINE__) const { |
| | | return this->variableExists(token.lexeme, context, file, line); |
| | | } |
| | | |
| | | 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); |
| | |
| | | |
| | | namespace ScriptInterpreterHelpers { |
| | | |
| | | static std::string extractSubstring(const std::string & str, size_t start, size_t end) { |
| | | static std::string extractSubstring(const std::string & str, const size_t & start, const size_t & end) { |
| | | if (start >= 0 && start < str.length() && end >= start && end < str.length()) { |
| | | return str.substr(start, end - start + 1); |
| | | } |
| | |
| | | |
| | | // 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) { |
| | | 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); |
| | | } |
| | | if (tokens[i].type != TokenType::RightParenthesis) { |
| | | 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); |
| | |
| | | |
| | | 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); |
| | |
| | | return arguments; |
| | | } |
| | | |
| | | [[nodiscard]] static std::string getFunctionBody(const std::vector<Token> & tokens, std::size_t & i) { |
| | | const size_t first_index = i; |
| | | 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) << std::endl; |
| | | |
| | | 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; |
| | | } |
| | |
| | | throw std::runtime_error("Unexpected end of file"); |
| | | break; |
| | | } |
| | | lines += tokens[i].lexeme + " "; |
| | | 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) << std::endl; |
| | | |
| | | if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) { |
| | | THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}"); |
| | | } |
| | | return lines; |
| | | }; |
| | | |
| | | }; // namespace ScriptInterpreterHelpers |