From cb3065c34756a70cb6006fc25777ce3e720ff1a8 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Sun, 13 Apr 2025 18:16:19 +0000
Subject: [PATCH] implement variable contexts, add function body store and parsing

---
 cli/main.cpp                     |    2 
 src/ScriptExceptionMacros.h      |    3 
 src/ScriptInterpreterHelpers.hpp |   59 ++++---
 src/Lexer.hpp                    |   13 +
 src/Lexer.cpp                    |  164 ++++++++++++++---------
 src/ScriptInterpreter.cpp        |  106 ++++++++-------
 CMakeLists.txt                   |    1 
 .clangd                          |    2 
 src/ScriptInterpreter.hpp        |   55 +++++++
 9 files changed, 260 insertions(+), 145 deletions(-)

diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..d96eb21
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,2 @@
+CompileFlags:
+  Add: [-Wunused]
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6d4bf2..a5af0a9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,7 @@
 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)
diff --git a/cli/main.cpp b/cli/main.cpp
index 1ec1928..cf15e33 100644
--- a/cli/main.cpp
+++ b/cli/main.cpp
@@ -53,7 +53,7 @@
         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;
diff --git a/src/Lexer.cpp b/src/Lexer.cpp
index c7067d7..6d003a3 100644
--- a/src/Lexer.cpp
+++ b/src/Lexer.cpp
@@ -35,31 +35,62 @@
     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      startCol = colNumber;
+    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;
         }
@@ -72,34 +103,46 @@
                 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      startCol = colNumber;
+    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() {
-    size_t startCol = colNumber;
+Token Lexer::variableToken() {
+    size_t startChar = charNumber;
+    size_t startCol  = colNumber;
     advance();  // Skip $
     std::string varName;
     if (isalpha(peek()) || peek() == '_') {
@@ -107,24 +150,30 @@
         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() {
-    size_t startCol = colNumber;
+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();
     }
@@ -135,16 +184,14 @@
             advance();
         }
         if (peek() == '$') {
-            return this->variableDeclaration(type);
+            return this->variableDeclarationToken(type);
         }
-        return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+        return createToken(TokenType::Identifier, lexeme);
     }
-
-    return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+    return createToken(TokenType::Identifier, lexeme);
 }
 
-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() == '_') {
@@ -154,40 +201,29 @@
         }
         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;
 }
 
@@ -205,84 +241,84 @@
 
     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;
 }
diff --git a/src/Lexer.hpp b/src/Lexer.hpp
index d374fa1..5648ab7 100644
--- a/src/Lexer.hpp
+++ b/src/Lexer.hpp
@@ -38,6 +38,19 @@
     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;
diff --git a/src/ScriptExceptionMacros.h b/src/ScriptExceptionMacros.h
index c2e754f..55dce1c 100644
--- a/src/ScriptExceptionMacros.h
+++ b/src/ScriptExceptionMacros.h
@@ -18,6 +18,9 @@
 #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__)
diff --git a/src/ScriptInterpreter.cpp b/src/ScriptInterpreter.cpp
index 92e1da6..3735ba0 100644
--- a/src/ScriptInterpreter.cpp
+++ b/src/ScriptInterpreter.cpp
@@ -35,8 +35,14 @@
             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");
 
@@ -81,67 +87,79 @@
 }
 
 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 {
@@ -153,8 +171,7 @@
 }
 
 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) {
@@ -162,12 +179,7 @@
         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);
@@ -176,13 +188,7 @@
                 }
             } 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);
@@ -191,7 +197,7 @@
                 }
             } 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");
@@ -199,7 +205,7 @@
             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);
     }
 }
 
@@ -224,24 +230,26 @@
     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) {
@@ -272,16 +280,13 @@
 }
 
 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];
+    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 {
@@ -300,8 +305,11 @@
     i++;  // Skip semicolon token
 }
 
-void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool ignore_tags) {
-    this->filename = filename;
+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();
 
diff --git a/src/ScriptInterpreter.hpp b/src/ScriptInterpreter.hpp
index 5ecc7c3..803ef66 100644
--- a/src/ScriptInterpreter.hpp
+++ b/src/ScriptInterpreter.hpp
@@ -1,7 +1,6 @@
 #ifndef SSCRIPTINTERPRETER_HPP
 #define SSCRIPTINTERPRETER_HPP
 #include <functional>
-#include <map>
 #include <memory>
 #include <string>
 #include <unordered_map>
@@ -18,7 +17,8 @@
 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;
@@ -29,13 +29,19 @@
     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;
     }
 
@@ -51,7 +57,8 @@
         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);
@@ -60,9 +67,47 @@
                 }
             }
         }
-        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);
diff --git a/src/ScriptInterpreterHelpers.hpp b/src/ScriptInterpreterHelpers.hpp
index 6b615c7..debd1bc 100644
--- a/src/ScriptInterpreterHelpers.hpp
+++ b/src/ScriptInterpreterHelpers.hpp
@@ -14,7 +14,7 @@
 
 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);
     }
@@ -37,28 +37,31 @@
 
     // 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);
     }
-    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 (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);
+        }
+
+        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 (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);
@@ -68,17 +71,19 @@
     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;
         }
@@ -86,14 +91,16 @@
             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

--
Gitblit v1.9.3