From 7d7a1e80c8a8c1e52446453d1b86d3c3b945ec29 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Sat, 12 Apr 2025 17:23:10 +0000
Subject: [PATCH] init

---
 src/ScriptExceptionMacros.h  |   19 
 src/Value.hpp                |  168 ++++++
 .gitignore                   |    3 
 cmake/options.h.in           |   12 
 .clang-tidy                  |   26 +
 src/SScriptInterpreter.hpp   |   99 +++
 .clang-format                |  160 ++++++
 src/BaseFunction.hpp         |   26 +
 CMakeLists.txt               |   65 ++
 cli/main.cpp                 |   54 ++
 src/Lexer.hpp                |   50 ++
 test_scripts/test1.ss        |   14 
 test_scripts/test2.ss        |   12 
 src/Lexer.cpp                |  246 +++++++++
 src/SScriptInterpreter.cpp   |  275 +++++++++++
 src/Builtins/PrintModule.hpp |   61 ++
 src/Token.hpp                |   92 +++
 src/VariableTypes.hpp        |   63 ++
 18 files changed, 1,445 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..7a24685
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,160 @@
+---
+Language:        Cpp
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: Left
+AlignConsecutiveAssignments: AcrossComments
+AlignConsecutiveBitFields: AcrossComments
+AlignConsecutiveDeclarations: AcrossComments
+AlignConsecutiveMacros: AcrossComments
+# AlignConsecutiveShortCaseStatements: AcrossComments
+AlignEscapedNewlines: Left # LeftWithLastLine
+AlignOperands:   Align
+AlignTrailingComments:
+  Kind: Always
+  OverEmptyLines: 1
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+# AllowBreakBeforeNoexceptSpecifier: OnlyWithParen
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: Inline
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: true
+BinPackArguments: true
+BinPackParameters: true # OnePerLine
+BitFieldColonSpacing: Both
+BreakBeforeBraces: Custom # Attach
+BraceWrapping:
+  AfterCaseLabel:  true
+  AfterClass:      false
+  AfterControlStatement: false
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile: false
+  IndentBraces:    false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
+# BreakAdjacentStringLiterals: true
+BreakAfterAttributes: Never
+BreakBeforeBinaryOperators: None
+BreakBeforeInlineASMColon: OnlyMultiline
+BreakBeforeTernaryOperators: false
+# BreakBinaryOperations: Never
+BreakConstructorInitializers: AfterColon
+# BreakFunctionDefinitionParameters: false
+BreakInheritanceList: AfterComma
+BreakStringLiterals: true
+# BreakTemplateDeclarations: Yes
+ColumnLimit:     120
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineBeforeAccessModifier: Leave
+EmptyLineAfterAccessModifier: Never
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+IncludeBlocks:   Regroup
+IncludeCategories:
+  - Regex:           '^<.*\.h>'
+    Priority:        1
+    SortPriority:    0
+  - Regex:           '^<.*'
+    Priority:        2
+    SortPriority:    0
+  - Regex:           '.*'
+    Priority:        3
+    SortPriority:    0
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseBlocks: true
+IndentCaseLabels: true
+IndentExternBlock: NoIndent
+IndentGotoLabels: false
+IndentPPDirectives: AfterHash
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+InsertBraces:    true # NOTE: may lead to incorrect formatting
+InsertNewlineAtEOF: true
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+LineEnding: LF
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PPIndentWidth: -1
+PackConstructorInitializers: CurrentLine
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Middle
+QualifierAlignment: Left
+#QualifierOrder: ['static', 'inline', 'friend', 'constexpr', 'const', 'volatile', 'type', 'restrict']
+RawStringFormats:
+  - Language:        Cpp
+    Delimiters:
+      - cc
+      - CC
+      - cpp
+      - Cpp
+      - CPP
+      - 'c++'
+      - 'C++'
+    CanonicalDelimiter: ''
+ReferenceAlignment: Middle
+ReflowComments:  false # IndentOnly
+SeparateDefinitionBlocks: Always
+SortIncludes:    CaseInsensitive
+SortUsingDeclarations: LexicographicNumeric
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles:  Never
+SpacesInContainerLiterals: true
+SpacesInLineCommentPrefix:
+  Minimum: 1
+  Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+Standard:        c++17
+TabWidth:        4
+UseTab:          Never
+WhitespaceSensitiveMacros: ['STRINGIZE']
+...
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..310c3d1
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,26 @@
+---
+Checks: >
+    bugprone-*,
+    -bugprone-easily-swappable-parameters,
+    -bugprone-implicit-widening-of-multiplication-result,
+    -bugprone-misplaced-widening-cast,
+    -bugprone-narrowing-conversions,
+    readability-*,
+    -readability-avoid-unconditional-preprocessor-if,
+    -readability-function-cognitive-complexity,
+    -readability-identifier-length,
+    -readability-implicit-bool-conversion,
+    -readability-magic-numbers,
+    -readability-uppercase-literal-suffix,
+    -readability-simplify-boolean-expr,
+    clang-analyzer-*,
+    -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
+    performance-*,
+    portability-*,
+    -portability-simd-intrinsics,
+    misc-*,
+    -misc-const-correctness,
+    -misc-non-private-member-variables-in-classes,
+    -misc-no-recursion,
+    -misc-use-anonymous-namespace,
+FormatStyle: none
diff --git a/.gitignore b/.gitignore
index 0def275..09f2655 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,6 @@
 *.exe
 *.out
 *.app
+.vscode
+.cache
+build
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..6d747b0
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,65 @@
+cmake_minimum_required(VERSION 3.16)
+project(sonyscript LANGUAGES CXX)
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+
+option(BUILD_CLI, "Build example commandline intrepeter" ON)
+option(BUILD_TESTS, "Build tests" OFF)
+option(BUILD_SHARED_LIBS "Build shared library" ON)
+option(BUILD_STATIC_LIBS "Build static library" ON)
+
+set(COMMENT_CHARACTER "#")
+
+message(STATUS "BUILD_CLI: ${BUILD_CLI}")
+message(STATUS "BUILD_TESTS: ${BUILD_TESTS}")
+message(STATUS "\tCOMMENT_CHARACTER: ${COMMENT_CHARACTER}")
+
+if (CMAKE_BUILD_TYPE STREQUAL "")
+    set(CMAKE_BUILD_TYPE Release)
+    message(STATUS "CMAKE_BUILD_TYPE is not set, defaulting to Release")
+endif()
+
+
+
+message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
+endif()
+
+configure_file("cmake/options.h.in" "include/options.h" @ONLY)
+include_directories(${CMAKE_BINARY_DIR}/include)
+include_directories(src)
+
+
+file(GLOB_RECURSE SOURCES
+    src/main.cpp
+    src/SScriptInterpreter.cpp
+    src/Lexer.cpp
+)
+
+if (BUILD_SHARED_LIBS)
+    add_library(${CMAKE_PROJECT_NAME} SHARED)
+    target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${SOURCES})
+    set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME})
+endif()
+if (BUILD_STATIC_LIBS)
+    add_library(${CMAKE_PROJECT_NAME}_static STATIC)
+    target_sources(${CMAKE_PROJECT_NAME}_static PRIVATE ${SOURCES})
+    set_target_properties(${CMAKE_PROJECT_NAME}_static PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME})
+endif()
+
+
+set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+
+if (BUILD_CLI)
+    add_executable(cli cli/main.cpp)
+    add_dependencies(cli ${CMAKE_PROJECT_NAME})
+    if (BUILD_SHARED_LIBS)
+        target_link_libraries(cli ${CMAKE_PROJECT_NAME})
+        else()
+        target_link_libraries(cli ${CMAKE_PROJECT_NAME}_static)
+    endif()
+endif()
\ No newline at end of file
diff --git a/cli/main.cpp b/cli/main.cpp
new file mode 100644
index 0000000..1e8a582
--- /dev/null
+++ b/cli/main.cpp
@@ -0,0 +1,54 @@
+#include <filesystem>
+#include <fstream>
+
+#include "Builtins/PrintModule.hpp"
+#include "SScriptInterpreter.hpp"
+
+static bool DEBUG = false;
+
+int main(int argc, char * argv[]) {
+    SScriptInterpreter interp;
+    interp.registerFunction("print", std::make_shared<PrintFunction>());
+
+
+    if (argc < 2) {
+        std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>" << std::endl;
+        return 1;
+    }
+    if (argc > 2) {
+        if (std::string(argv[1]) == "-d" || std::string(argv[1]) == "--debug") {
+            DEBUG = true;
+        } else {
+            std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>" << std::endl;
+            return 1;
+        }
+    }
+    if (argc > 3) {
+        std::cerr << "Error: Too many arguments." << std::endl;
+        return 1;
+    }
+
+    if (!std::filesystem::exists(argv[2])) {
+        std::cerr << "Error: File " << argv[2] << " does not exist." << std::endl;
+        return 1;
+    }
+    // get the absolute path of the file
+    const std::string filename = std::filesystem::canonical(argv[2]).string();
+
+    try {
+        std::ifstream file(filename);
+        if (!file.is_open()) {
+            std::cerr << "Error: Could not open file " << filename << std::endl;
+            return 1;
+        }
+
+        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+
+        interp.executeScript(content, filename, DEBUG);
+    } catch (const std::exception & e) {
+        std::cerr << "Parser error: " << e.what() << std::endl;
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/cmake/options.h.in b/cmake/options.h.in
new file mode 100644
index 0000000..7f4db26
--- /dev/null
+++ b/cmake/options.h.in
@@ -0,0 +1,12 @@
+/*
+* THIS IS A GENERATED FILE. DO NOT EDIT.
+*/
+#ifdef WIN32
+const char EOL = '\r\n';
+#else
+const char EOL = '\n';
+#endif
+const char   COMMENT_CHARACTER = '@COMMENT_CHARACTER@';
+const static char * PARSER_OPEN_TAG   = "<?void";
+const static char * PARSER_CLOSE_TAG  = "?>";
+#cmakedefine BUILD_TYPE @CMAKE_BUILD_TYPE @
diff --git a/src/BaseFunction.hpp b/src/BaseFunction.hpp
new file mode 100644
index 0000000..fb82971
--- /dev/null
+++ b/src/BaseFunction.hpp
@@ -0,0 +1,26 @@
+// ScriptFunction.hpp
+#ifndef SCRIPT_FUNCTION_HPP
+#define SCRIPT_FUNCTION_HPP
+
+#include <vector>
+
+#include "Token.hpp"
+#include "Value.hpp"
+
+
+class SScriptInterpreter;
+
+class BaseFunction {
+  protected:
+    std::string name;
+  public:
+    virtual ~BaseFunction()                                                       = default;
+    virtual void  validate(const std::vector<Token> & tokens, size_t & i) const   = 0;
+    virtual Value call(const std::vector<Value> & args, bool debug = false) const = 0;
+
+    template <typename FuncClass> void registerFunctionTo(SScriptInterpreter & interp) {
+        FuncClass::registerTo(interp);
+    }
+};
+
+#endif  // SCRIPT_FUNCTION_HPP
diff --git a/src/Builtins/PrintModule.hpp b/src/Builtins/PrintModule.hpp
new file mode 100644
index 0000000..e3a53e4
--- /dev/null
+++ b/src/Builtins/PrintModule.hpp
@@ -0,0 +1,61 @@
+#ifndef PRINTFUNCTION_HPP
+#define PRINTFUNCTION_HPP
+
+#include <iostream>
+
+#include "BaseFunction.hpp"
+#include "ScriptExceptionMacros.h"
+#include "SScriptInterpreter.hpp"
+#include "Token.hpp"
+#include "Value.hpp"
+
+class PrintFunction : public BaseFunction {
+  private:
+    const std::string name = "print";
+  public:
+    void validate(const std::vector<Token> & tokens, size_t & i) const override {
+        auto index = i;
+        if (tokens[index].type != TokenType::Identifier) {
+            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "identifier: " + name);
+        }
+        index++;  // skip function name
+        if (tokens[index].type != TokenType::LeftParenthesis) {
+            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "('");
+        }
+        index++;  // skip '('
+        if (tokens[index].type != TokenType::StringLiteral && tokens[index].type != TokenType::Variable &&
+            tokens[index].type != TokenType::IntLiteral && tokens[index].type != TokenType::DoubleLiteral) {
+            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "string, int, double or variable as argument");
+        }
+        size_t count = 0;
+        while (tokens[index].type != TokenType::RightParenthesis) {
+            if (tokens[index].type == TokenType::StringLiteral || tokens[index].type == TokenType::Variable ||
+                tokens[index].type == TokenType::IntLiteral || tokens[index].type == TokenType::DoubleLiteral) {
+                count++;
+                index++;
+            } else if (tokens[index].type == TokenType::Comma) {
+                index++;
+            } else {
+                THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "string, int, double or variable as argument");
+            }
+        }
+        if (count == 0) {
+            throw std::runtime_error("print() requires at least one argument at");
+        }
+        index++;  // skip ')'
+        if (tokens[index].type == TokenType::Semicolon) {
+            index++;
+        } else {
+            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], ";");
+        }
+    }
+
+    Value call(const std::vector<Value> & args, bool debug = false) const override {
+        for (const auto & arg : args) {
+            std::cout << arg.ToString();
+        }
+        return Value();
+    }
+};
+
+#endif  // PRINTFUNCTION_HPP
diff --git a/src/Lexer.cpp b/src/Lexer.cpp
new file mode 100644
index 0000000..62e110c
--- /dev/null
+++ b/src/Lexer.cpp
@@ -0,0 +1,246 @@
+#include "Lexer.hpp"
+
+#include <cctype>
+
+#include "Value.hpp"
+
+Lexer::Lexer(const std::string & source, const std::string & filename) :
+    src(source),
+    pos(0),
+    filename(filename),
+    lineNumber(1),
+    colNumber(1),
+    charNumber(0) {}
+
+char Lexer::peek() const {
+    return pos < src.size() ? src[pos] : '\0';
+}
+
+char Lexer::advance() {
+    if (pos >= src.size()) {
+        return '\0';
+    }
+    char c = src[pos++];
+    if (c == '\n') {
+        this->lineNumber++;
+        this->colNumber = 1;
+    } else {
+        this->colNumber++;
+    }
+    this->charNumber++;
+    return c;
+}
+
+bool Lexer::isAtEnd() const {
+    return pos >= src.size();
+}
+
+Token Lexer::string() {
+    std::string result;
+    size_t      startCol = colNumber;
+    advance();  // Skip opening quote
+    while (!isAtEnd() && peek() != '"') {
+        result += advance();
+    }
+    if (isAtEnd() || peek() != '"') {
+        return { TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol };
+    }
+    advance();  // Skip closing quote
+    return { TokenType::StringLiteral, result, filename, lineNumber, startCol };
+}
+
+Token Lexer::number() {
+    std::string result;
+    std::string found;
+    TokenType   type             = TokenType::Unknown;
+    bool        decimalPointSeen = false;
+    size_t      startCol         = colNumber;
+
+    while (std::isdigit(peek()) || peek() == '.') {
+        if (peek() == '.') {
+            if (decimalPointSeen) {
+                return { TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol };
+            }
+            decimalPointSeen = true;
+        }
+        found.append(1, advance());
+    }
+
+    if (!found.empty()) {
+        if (found.find('.') == std::string::npos) {
+            if (is_number<int>(found)) {
+                result = found;
+                type   = TokenType::IntLiteral;
+            } else {
+                return { TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol };
+            }
+        } else {
+            if (is_number<double>(found)) {
+                result = found;
+                type   = TokenType::DoubleLiteral;
+            } else {
+                return { TokenType::Unknown, "Invalid double", filename, lineNumber, startCol };
+            }
+        }
+    } else {
+        return { TokenType::Unknown, "Expected number", filename, lineNumber, startCol };
+    }
+
+    return { type, result, filename, lineNumber, startCol };
+}
+
+Token Lexer::identifier() {
+    std::string result;
+    size_t      startCol = colNumber;
+    while (isalnum(peek()) || peek() == '_') {
+        result += advance();
+    }
+    return { TokenType::Identifier, result, filename, lineNumber, startCol };
+}
+
+Token Lexer::variable() {
+    size_t startCol = colNumber;
+    advance();  // Skip $
+    std::string varName;
+    if (isalpha(peek()) || peek() == '_') {
+        varName += advance();
+        while (isalnum(peek()) || peek() == '_') {
+            varName += advance();
+        }
+        return { TokenType::Variable, varName, filename, lineNumber, startCol };
+    }
+    return { TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol };
+}
+
+Token Lexer::comment() {
+    size_t startCol = colNumber;
+    advance();  // Skip #
+    std::string commentText;
+    while (!isAtEnd() && peek() != '\n') {
+        commentText += advance();
+    }
+    return { TokenType::Comment, commentText, filename, lineNumber, startCol };
+}
+
+Token Lexer::keywordOrIdentifier() {
+    std::string lexeme;
+    size_t      startCol = colNumber;
+    while (isalpha(peek())) {
+        lexeme += advance();
+    }
+
+    if (Variables::StringToTypeMap.contains(lexeme)) {
+        const auto type = Variables::StringToTypeMap.at(lexeme);
+        while (isspace(peek())) {
+            advance();
+        }
+        if (peek() == '$') {
+            return variableDeclaration(type);
+        }
+        return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+    }
+    return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+}
+
+Token Lexer::variableDeclaration(Variables::Type type) {
+    size_t startCol = colNumber;
+    advance();  // Skip $
+    std::string varName;
+    if (isalpha(peek()) || peek() == '_') {
+        varName += advance();
+        while (isalnum(peek()) || peek() == '_') {
+            varName += advance();
+        }
+        switch (type) {
+            case Variables::Type::VT_INT:
+                return { TokenType::IntDeclaration, varName, filename, lineNumber, startCol };
+            case Variables::Type::VT_DOUBLE:
+                return { TokenType::DoubleDeclaration, varName, filename, lineNumber, startCol };
+            case Variables::Type::VT_STRING:
+                return { TokenType::StringDeclaration, varName, filename, lineNumber, startCol };
+            default:
+                return { TokenType::Unknown, "Invalid variable type in declaration", filename, lineNumber, startCol };
+        }
+    } else {
+        return { TokenType::Unknown, "$ followed by invalid character in declaration", filename, lineNumber, startCol };
+    }
+}
+
+Token Lexer::singleCharToken(TokenType type, const std::string & lexeme) {
+    size_t startCol = colNumber;
+    advance();
+    return { type, lexeme, filename, lineNumber, startCol };
+}
+
+bool Lexer::matchSequence(const std::string & sequence) const {
+    if (pos + sequence.length() > src.length()) {
+        return false;
+    }
+    return src.substr(pos, sequence.length()) == sequence;
+}
+
+void Lexer::matchAndConsume(const std::string & sequence) {
+    if (matchSequence(sequence)) {
+        for (size_t i = 0; i < sequence.length(); ++i) {
+            advance();
+        }
+    }
+}
+
+std::vector<Token> Lexer::tokenize() {
+    std::vector<Token> tokens;
+
+    while (!isAtEnd()) {
+        char c = peek();
+        if (isspace(c)) {
+            advance();
+            continue;
+        }
+        if (c == '\n') {
+            tokens.push_back(singleCharToken(TokenType::EndOfLine, ""));
+            continue;
+        }
+        if (c == '#') {
+            tokens.push_back(comment());
+            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 });
+            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 });
+            continue;
+        }
+        if (isalpha(c)) {
+            tokens.push_back(keywordOrIdentifier());
+        } else if (c == '$') {
+            tokens.push_back(variable());
+        } else if (isdigit(c)) {
+            tokens.push_back(number());
+        } else if (c == '"' || c == '\'') {
+            tokens.push_back(string());
+        } else if (c == '(') {
+            tokens.push_back(singleCharToken(TokenType::LeftParenthesis, "("));
+        } else if (c == ')') {
+            tokens.push_back(singleCharToken(TokenType::RightParenthesis, ")"));
+        } else if (c == ',') {
+            tokens.push_back(singleCharToken(TokenType::Comma, ","));
+        } else if (c == ';') {
+            tokens.push_back(singleCharToken(TokenType::Semicolon, ";"));
+        } else if (c == '=') {
+            tokens.push_back(singleCharToken(TokenType::Equals, "="));
+        } else {
+            tokens.push_back({ TokenType::Unknown, std::string(1, c), filename, lineNumber, colNumber });
+            advance();
+        }
+    }
+
+    tokens.push_back({ TokenType::EndOfFile, "", filename, lineNumber, colNumber });
+    return tokens;
+}
diff --git a/src/Lexer.hpp b/src/Lexer.hpp
new file mode 100644
index 0000000..b5ac4e3
--- /dev/null
+++ b/src/Lexer.hpp
@@ -0,0 +1,50 @@
+#ifndef LEXER_HPP
+#define LEXER_HPP
+
+#include <istream>
+#include <sstream>
+#include <vector>
+
+#include "VariableTypes.hpp"
+#include "options.h"
+#include "Token.hpp"
+
+class Lexer {
+  public:
+    Lexer(const std::string & source, const std::string & filename);
+    std::vector<Token> tokenize();
+
+  private:
+    const std::string & src;
+    const std::string & filename;
+    size_t              pos;
+    int                 lineNumber = 1;
+    size_t              colNumber  = 1;
+    size_t              charNumber = 1;
+
+    char peek() const;
+    char advance();
+    bool isAtEnd() const;
+
+    Token string();
+    Token number();
+    Token identifier();
+    Token variable();
+    Token comment();
+    Token keywordOrIdentifier();
+    Token singleCharToken(TokenType type, const std::string & lexeme);
+    bool matchSequence(const std::string & sequence) const;
+    Token variableDeclaration(Variables::Type type);
+    void matchAndConsume(const std::string & sequence);
+
+
+    // validate number types from string
+    template <typename Numeric> static bool is_number(const std::string & s) {
+        Numeric n;
+        return ((std::istringstream(s) >> n >> std::ws).eof());
+    }
+
+    bool matchSequence(const std::string & sequence) { return src.substr(pos, sequence.length()) == sequence; }
+};
+
+#endif  // LEXER_HPP
diff --git a/src/SScriptInterpreter.cpp b/src/SScriptInterpreter.cpp
new file mode 100644
index 0000000..bc71f5b
--- /dev/null
+++ b/src/SScriptInterpreter.cpp
@@ -0,0 +1,275 @@
+#include "SScriptInterpreter.hpp"
+
+#include <iostream>
+#include <stdexcept>
+#include <unordered_map>
+#include <utility>
+
+#include "Lexer.hpp"
+#include "ScriptExceptionMacros.h"
+#include "Value.hpp"
+
+void SScriptInterpreter::registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn) {
+    functionObjects[name] = std::move(fn);
+}
+
+Value SScriptInterpreter::evaluateExpression(const Token & token) const {
+    if (token.type == TokenType::StringLiteral) {
+        return Value::fromString(token.lexeme);
+    }
+    if (token.type == TokenType::IntLiteral) {
+        try {
+            return Value::fromInt(std::stoi(token.lexeme));
+        } 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(std::stod(token.lexeme));
+        } 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) {
+        if (variables.find(token.lexeme) != variables.end()) {
+            return variables.at(token.lexeme);
+        }
+        THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token);
+    } else {
+        THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable");
+    }
+    return Value();
+}
+
+std::vector<Value> SScriptInterpreter::parseArguments(const std::vector<Token> & tokens,
+                                                      std::size_t &              current_index) const {
+    std::vector<Value> args;
+
+    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 ')'
+    current_index = current_index;
+
+    return args;
+}
+
+void SScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
+    const auto varName = tokens[i].lexeme;
+    const auto varType = tokens[i].variableType;
+
+    i++;      // Skip variable name
+    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
+        i++;  // Skip '='
+
+        if (i < tokens.size() && tokens[i].type == TokenType::Variable) {
+            if (variables.find(tokens[i].lexeme) == variables.end()) {
+                THROW_UNDEFINED_VARIABLE_ERROR(tokens[i].lexeme, tokens[i]);
+            } else {
+                if (variables[tokens[i].lexeme].type != Variables::Type::VT_STRING) {
+                    THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING),
+                                                        tokens[i].lexeme, variables[tokens[i].lexeme].TypeToString(),
+                                                        tokens[i]);
+                }
+
+                variables[varName] = variables[tokens[i].lexeme];
+                i++;  // Skip variable name
+                expectSemicolon(tokens, i, "after string variable declaration");
+            }
+        } else if (i < tokens.size() && tokens[i].type == TokenType::StringLiteral) {
+            variables[varName] = Value::fromString(tokens[i].lexeme);
+            i++;  // Skip string literal
+            expectSemicolon(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 SScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & 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 {
+                    if (variables.find(varName) != variables.end()) {
+                        THROW_VARIABLE_REDEFINITION_ERROR(varName, tokens[i]);
+                    }
+                    variables[varName] = Value::fromInt(std::stoi(tokens[i].lexeme));
+                    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 {
+                    if (variables.find(varName) != variables.end()) {
+                        THROW_VARIABLE_REDEFINITION_ERROR(varName, tokens[i]);
+                    }
+                    variables[varName] = Value::fromDouble(std::stod(tokens[i].lexeme));
+                    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]);
+            }
+            expectSemicolon(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 SScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) {
+    std::string funcName = tokens[i].lexeme;
+    auto        it       = functionObjects.find(funcName);
+    if (it == functionObjects.end()) {
+        throw std::runtime_error("Unknown function: " + funcName);
+    }
+    it->second->validate(tokens, i);
+    std::vector<Value> args = parseArguments(tokens, i);
+    it->second->call(args);
+    if (i < tokens.size() && tokens[i].type == TokenType::Semicolon) {
+        i++;  // Skip ';' after function call
+    }
+}
+
+void SScriptInterpreter::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;
+    i++;      // Skip variable token to avoid infinite loop
+    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
+        i++;  // Skip '='
+        if (i < tokens.size()) {
+            if (variables.find(varName) == variables.end()) {
+                THROW_UNDEFINED_VARIABLE_ERROR(varName, tokens[i]);
+            }
+            variables[varName] = evaluateExpression(tokens[i]);
+            i++;  // Skip value
+            expectSemicolon(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 SScriptInterpreter::handleComment(std::size_t & i) {
+    i++;  // Skip comment token
+}
+
+void SScriptInterpreter::handleSemicolon(std::size_t & i) {
+    i++;  // Skip semicolon token
+}
+
+void SScriptInterpreter::expectSemicolon(const std::vector<Token> & tokens, std::size_t & i,
+                                         const std::string & message) const {
+    if (i >= tokens.size() || tokens[i].type != TokenType::Semicolon) {
+        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "; " + message);
+    } else {
+        i++;  // Skip ';'
+    }
+}
+
+void SScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool debug) {
+    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) {
+            // Csak kiíratás, ha nem vagyunk script tagben
+            std::cout << token.lexeme;
+            i++;
+            continue;
+        }
+
+        // A szokásos feldolgozás csak ha belül vagyunk
+        switch (token.type) {
+            case TokenType::StringDeclaration:
+                handleStringDeclaration(tokens, i);
+                break;
+            case TokenType::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 std::runtime_error("Unexpected token inside script: " + token.lexeme);
+        }
+    }
+}
diff --git a/src/SScriptInterpreter.hpp b/src/SScriptInterpreter.hpp
new file mode 100644
index 0000000..f9cb33f
--- /dev/null
+++ b/src/SScriptInterpreter.hpp
@@ -0,0 +1,99 @@
+#ifndef SSCRIPTINTERPRETER_HPP
+#define SSCRIPTINTERPRETER_HPP
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "BaseFunction.hpp"
+#include "Token.hpp"
+#include "Value.hpp"
+
+using FunctionValidator = std::function<void(const std::vector<Token> &, size_t &)>;
+
+class SScriptInterpreter {
+  public:
+    void registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn);
+    void executeScript(const std::string & source, const std::string & filenaneame, bool debug = false);
+
+    static void throwUnexpectedTokenError(const Token & token, const std::string & expected = "",
+                                          const std::string & file = "", const int & line = 0) {
+        const std::string error_content =
+            "unexpected token: '" + token.lexeme + "' type: " + tokenTypeNames.at(token.type) +
+            (expected.empty() ? "" : ", expected: '" + expected + "'") + " in file: " + token.file + ":" +
+            std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
+#if BUILD_TYPE == Debug
+        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
+#else
+        const std::string error_message = error_content;
+#endif
+        throw std::runtime_error(error_message);
+    };
+
+    static void throwUndefinedVariableError(const std::string & name, const Token & token, const std::string & file,
+                                            const int & line = 0) {
+        const std::string error_content = "undefined variable: '$" + name + "' in file: " + token.file + ":" +
+                                          std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
+#if BUILD_TYPE == Debug
+        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
+#else
+        const std::string error_message = error_content;
+#endif
+        throw std::runtime_error(error_message);
+    }
+
+    static void throwVariableTypeMissmatchError(const std::string & target_variable_name,
+                                               const std::string & target_type,
+                                               const std::string & source_variable_name,
+                                               const std::string & source_type, const Token & token,
+                                               const std::string & file = "", const int & line = 0) {
+        std::string error_content =
+            "variable type missmatch: '$" + target_variable_name + "' declared type: '" + target_type + "'";
+        if (!source_variable_name.empty()) {
+            error_content += " source variable: '" + source_variable_name + "'";
+        }
+        if (!source_type.empty()) {
+            error_content += " assigned type: '" + source_type + "'";
+        }
+
+        error_content += " in file: " + token.file + ":" + std::to_string(token.lineNumber) + ":" +
+                         std::to_string(token.columnNumber);
+#if BUILD_TYPE == Debug
+        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
+#else
+        const std::string error_message = error_content;
+#endif
+        throw std::runtime_error(error_message);
+    }
+
+    static void throwVariableRedefinitionError(const std::string & name, const Token & token,
+                                               const std::string & file = "", const int line = 0) {
+        const std::string error_content = "variable alread defined: " + name + " in file: " + token.file + ":" +
+                                          std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
+#if BUILD_TYPE == Debug
+        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
+#else
+        const std::string error_message = error_content;
+#endif
+        throw std::runtime_error(error_message);
+    }
+
+  private:
+    std::unordered_map<std::string, FunctionValidator>             functionValidators;
+    std::unordered_map<std::string, std::shared_ptr<BaseFunction>> functionObjects;
+    std::unordered_map<std::string, Value>                         variables;
+
+    std::vector<Value> parseArguments(const std::vector<Token> & tokens, std::size_t & current_index) const;
+    Value              evaluateExpression(const Token & token) const;
+    // type handlers
+    void expectSemicolon(const std::vector<Token> & tokens, std::size_t & i, const std::string & message) const;
+    void handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i);
+    void handleVariableReference(const std::vector<Token> & tokens, std::size_t & i);
+    void handleComment(std::size_t & i);
+    void handleSemicolon(std::size_t & i);
+    void handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i);
+    void handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type);
+};
+
+#endif  // SSCRIPTINTERPRETER_HPP
diff --git a/src/ScriptExceptionMacros.h b/src/ScriptExceptionMacros.h
new file mode 100644
index 0000000..8d9a7a6
--- /dev/null
+++ b/src/ScriptExceptionMacros.h
@@ -0,0 +1,19 @@
+#ifndef SCRIPT_EXCEPTION_MACROS_H
+#define SCRIPT_EXCEPTION_MACROS_H
+
+#define THROW_UNEXPECTED_TOKEN_ERROR(token, expected) \
+    SScriptInterpreter::throwUnexpectedTokenError(token, expected, __FILE__, __LINE__)
+
+#define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \
+    SScriptInterpreter::throwUndefinedVariableError(name, token, __FILE__, __LINE__)
+
+#define THROW_VARIABLE_TYPE_MISSMATCH_ERROR(target_variable_name, target_variable_type, source_variable_name,       \
+                                            source_variable_type, token)                                            \
+    SScriptInterpreter::throwVariableTypeMissmatchError(target_variable_name, target_variable_type,                  \
+                                                       source_variable_name, source_variable_type, token, __FILE__, \
+                                                       __LINE__)
+
+#define THROW_VARIABLE_REDEFINITION_ERROR(name, token) \
+    SScriptInterpreter::throwVariableRedefinitionError(name, token, __FILE__, __LINE__)
+
+#endif  // SCRIPT_EXCEPTION_MACROS_H
diff --git a/src/Token.hpp b/src/Token.hpp
new file mode 100644
index 0000000..b90fa92
--- /dev/null
+++ b/src/Token.hpp
@@ -0,0 +1,92 @@
+#ifndef TOKEN_HPP
+#define TOKEN_HPP
+#include <cstdint>
+#include <iostream>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+
+#include "VariableTypes.hpp"
+
+enum class TokenType : std::uint8_t {
+    ParserOpenTag,
+    ParserCloseTag,
+    FileClose,
+    Identifier,
+    StringLiteral,
+    IntLiteral,
+    DoubleLiteral,
+    LeftParenthesis,
+    RightParenthesis,
+    Comma,
+    Semicolon,
+    Variable,           // $variable
+    VariableSign,       // $ variable start sign
+    StringDeclaration,  // string $variable
+    IntDeclaration,     // int $variable
+    DoubleDeclaration,  // double $variable
+    Equals,             // = jel
+    EndOfFile,
+    EndOfLine,
+    Comment,
+    Unknown
+};
+
+const static std::unordered_map<TokenType, std::string> tokenTypeNames = {
+    { TokenType::ParserOpenTag,     "ParserOpenTag"     },
+    { TokenType::ParserCloseTag,    "ParserCloseTag"    },
+    { TokenType::FileClose,         "FileClose"         },
+    { TokenType::Identifier,        "Identifier"        },
+    { TokenType::StringLiteral,     "StringLiteral"     },
+    { TokenType::IntLiteral,        "IntLiteral"        },
+    { TokenType::DoubleLiteral,     "DoubleLiteral"     },
+    { TokenType::LeftParenthesis,   "LeftParenthesis"   },
+    { TokenType::RightParenthesis,  "RightParenthesis"  },
+    { TokenType::Comma,             "Comma"             },
+    { TokenType::Semicolon,         "Semicolon"         },
+    { TokenType::Variable,          "Variable"          },
+    { TokenType::VariableSign,      "VariableSign"      },
+    { TokenType::StringDeclaration, "StringDeclaration" },
+    { TokenType::IntDeclaration,    "IntDeclaration"    },
+    { TokenType::DoubleDeclaration, "DoubleDeclaration" },
+    { TokenType::Equals,            "Equals"            },
+    { TokenType::EndOfFile,         "EndOfFile"         },
+    { TokenType::EndOfLine,         "EndOfLine"         },
+    { TokenType::Comment,           "Comment"           },
+    { TokenType::Unknown,           "Unknown"           }
+};
+
+static const std::unordered_map<TokenType, Variables::Type> tokenTypeToVariableType = {
+    { TokenType::StringLiteral, Variables::Type::VT_STRING },
+    { TokenType::IntLiteral,    Variables::Type::VT_INT    },
+    { TokenType::DoubleLiteral, Variables::Type::VT_DOUBLE }
+};
+
+[[nodiscard]] static inline Variables::Type getVariableTypeFromTokenType(TokenType type) {
+    auto it = tokenTypeToVariableType.find(type);
+    if (it != tokenTypeToVariableType.end()) {
+        return it->second;
+    }
+
+    return Variables::Type::VT_NOT_DEFINED;
+}
+
+[[nodiscard]] static inline std::string getVariableTypeFromTokenTypeAsString(TokenType type) {
+    return Variables::TypeToString(getVariableTypeFromTokenType(type));
+}
+
+struct Token {
+    TokenType       type;
+    std::string     lexeme;
+    std::string     file;
+    int             lineNumber;
+    size_t          columnNumber;
+    Variables::Type variableType = Variables::Type::VT_NULL;
+
+    [[nodiscard]] std::string getTypeName() const { return tokenTypeNames.at(type); }
+
+    [[nodiscard]] std::string getVariableTypeName() const { return Variables::TypeToString(variableType); }
+};
+
+#endif  // TOKEN_HPP
diff --git a/src/Value.hpp b/src/Value.hpp
new file mode 100644
index 0000000..2a15758
--- /dev/null
+++ b/src/Value.hpp
@@ -0,0 +1,168 @@
+#ifndef VALUE_HPP
+#define VALUE_HPP
+#include <cstdint>
+#include <string>
+#include <variant>
+
+#include "VariableTypes.hpp"
+
+class Value {
+  public:
+    Variables::Type          type = Variables::Type::VT_NULL;
+    Variables::DataContainer data;
+
+    Value() : type(Variables::Type::VT_NULL) {}
+
+    Value(Variables::Type t, double val) : type(t), data(std::move(val)) {}
+
+    Value(Variables::Type t, int val) : type(t), data(std::move(val)) {}
+
+    Value(Variables::Type t, const std::string & val) : type(t), data(val) {}
+
+    Value(Variables::Type t, bool val) : type(t), data(std::move(val)) {}
+
+    static Value fromInt(int val) { return Value(Variables::Type::VT_INT, val); }
+
+    static Value fromDouble(double val) { return Value(Variables::Type::VT_DOUBLE, val); }
+
+    static Value fromString(const std::string & val) { return { Variables::Type::VT_STRING, val }; }
+
+    static Value fromBoolean(bool val) { return { Variables::Type::VT_BOOLEAN, val }; }
+
+    std::string ToString() const { return decodeEscapes(Variables::ToString(data, type)); }
+
+    std::string TypeToString() const { return Variables::TypeToString(type); }
+
+  private:
+    Value(Variables::Type t, std::variant<int, double, std::string, bool> && val) : type(t), data(std::move(val)) {}
+
+    static std::string decodeEscapes(const std::string & input) {
+        std::string result;
+        size_t      i = 0;
+
+        auto hexToChar = [](const std::string & hex) -> char {
+            return static_cast<char>(std::stoi(hex, nullptr, 16));
+        };
+
+        auto hexToUTF8 = [](uint32_t codepoint) -> std::string {
+            std::string out;
+            if (codepoint <= 0x7F) {
+                out += static_cast<char>(codepoint);
+            } else if (codepoint <= 0x7FF) {
+                out += static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F));
+                out += static_cast<char>(0x80 | (codepoint & 0x3F));
+            } else if (codepoint <= 0xFFFF) {
+                out += static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F));
+                out += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
+                out += static_cast<char>(0x80 | (codepoint & 0x3F));
+            } else if (codepoint <= 0x10FFFF) {
+                out += static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07));
+                out += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F));
+                out += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
+                out += static_cast<char>(0x80 | (codepoint & 0x3F));
+            }
+            return out;
+        };
+
+        while (i < input.size()) {
+            if (input[i] == '\\' && i + 1 < input.size()) {
+                char esc = input[i + 1];
+
+                // Standard short escape sequences
+                switch (esc) {
+                    case 'n':
+                        result += '\n';
+                        i += 2;
+                        continue;
+                    case 't':
+                        result += '\t';
+                        i += 2;
+                        continue;
+                    case 'r':
+                        result += '\r';
+                        i += 2;
+                        continue;
+                    case 'b':
+                        result += '\b';
+                        i += 2;
+                        continue;
+                    case 'f':
+                        result += '\f';
+                        i += 2;
+                        continue;
+                    case 'v':
+                        result += '\v';
+                        i += 2;
+                        continue;
+                    case 'a':
+                        result += '\a';
+                        i += 2;
+                        continue;
+                    case '\\':
+                        result += '\\';
+                        i += 2;
+                        continue;
+                    case '"':
+                        result += '"';
+                        i += 2;
+                        continue;
+                    case '\'':
+                        result += '\'';
+                        i += 2;
+                        continue;
+                    case '?':
+                        result += '?';
+                        i += 2;
+                        continue;
+                    case '0':
+                        result += '\0';
+                        i += 2;
+                        continue;
+                    case 'x':
+                        {
+                            // Hexadecimal escape: \xHH
+                            size_t      j = i + 2;
+                            std::string hex;
+                            while (j < input.size() && std::isxdigit(input[j]) && hex.size() < 2) {
+                                hex += input[j++];
+                            }
+                            if (!hex.empty()) {
+                                result += hexToChar(hex);
+                                i = j;
+                                continue;
+                            }
+                        }
+                        break;
+                    case 'u':
+                    case 'U':
+                        {
+                            // Unicode escape: \uHHHH or \UHHHHHHHH
+                            size_t      expected_len = (esc == 'u') ? 4 : 8;
+                            size_t      j            = i + 2;
+                            std::string hex;
+                            while (j < input.size() && std::isxdigit(input[j]) && hex.size() < expected_len) {
+                                hex += input[j++];
+                            }
+                            if (hex.size() == expected_len) {
+                                uint32_t codepoint = std::stoul(hex, nullptr, 16);
+                                result += hexToUTF8(codepoint);
+                                i = j;
+                                continue;
+                            }
+                        }
+                        break;
+                }
+
+                // Ha ide jutunk, ismeretlen vagy érvénytelen escape
+                result += '\\';
+                result += esc;
+                i += 2;
+            } else {
+                result += input[i++];
+            }
+        }
+
+        return result;
+    }
+};
+#endif  // VALUE_HPP
diff --git a/src/VariableTypes.hpp b/src/VariableTypes.hpp
new file mode 100644
index 0000000..6a3d5f1
--- /dev/null
+++ b/src/VariableTypes.hpp
@@ -0,0 +1,63 @@
+#ifndef VARIABLE_TYPES_HPP
+#define VARIABLE_TYPES_HPP
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <variant>
+
+namespace Variables {
+
+using DataContainer = std::variant<int, double, std::string, bool>;
+
+enum class Type : std::uint8_t { VT_INT, VT_DOUBLE, VT_STRING, VT_BOOLEAN, VT_NULL, VT_NOT_DEFINED };
+
+const std::unordered_map<std::string, Type> StringToTypeMap = {
+    { "int",         Type::VT_INT         },
+    { "double",      Type::VT_DOUBLE      },
+    { "string",      Type::VT_STRING      },
+    { "boolean",     Type::VT_BOOLEAN     },
+    { "null",        Type::VT_NULL        },
+    { "not_defined", Type::VT_NOT_DEFINED },
+};
+const std::unordered_map<Type, std::string> StypeToStringMap = {
+    { Type::VT_INT,         "int"         },
+    { Type::VT_DOUBLE,      "double"      },
+    { Type::VT_STRING,      "string"      },
+    { Type::VT_BOOLEAN,     "boolean"     },
+    { Type::VT_NULL,        "null"        },
+    { Type::VT_NOT_DEFINED, "not_defined" },
+};
+
+inline static std::string TypeToString(Type type) {
+    if (StypeToStringMap.find(type) != StypeToStringMap.end()) {
+        return StypeToStringMap.at(type);
+    }
+    return "null";
+};
+
+inline static Type StringToType(const std::string & type) {
+    if (StringToTypeMap.find(type) != StringToTypeMap.end()) {
+        return StringToTypeMap.at(type);
+    }
+    return Type::VT_NULL;
+};
+
+inline static std::string ToString(const DataContainer & data, const Type & type) {
+    switch (type) {
+        case Type::VT_INT:
+            return std::to_string(std::get<int>(data));
+        case Type::VT_DOUBLE:
+            return std::to_string(std::get<double>(data));
+        case Type::VT_STRING:
+            return std::get<std::string>(data);
+        case Type::VT_BOOLEAN:
+            return std::get<bool>(data) ? "true" : "false";
+        case Type::VT_NULL:
+        default:
+            return "null";
+    }
+};
+
+};  // namespace Variables
+#endif  // VARIABLE_TYPES_HPP
diff --git a/test_scripts/test1.ss b/test_scripts/test1.ss
new file mode 100644
index 0000000..ca2de5f
--- /dev/null
+++ b/test_scripts/test1.ss
@@ -0,0 +1,14 @@
+<?void
+string $name = "World 😀"; # world test
+string $greeting = "Hello ";
+string $smiley = "😀 = \\U0001F600 = \U0001F600\n";
+int $number = 123;
+double $number2 = 12.3;
+
+
+print("The number: ", $number, "\n");
+print("The number2: ", $number2, "\n");
+
+print("Unicode: \u00E9 \U0001F600, hex: \x41, newline:\nEnd\t",$greeting, $name, "\n\nSmiley test: ", $smiley);
+
+?>
\ No newline at end of file
diff --git a/test_scripts/test2.ss b/test_scripts/test2.ss
new file mode 100644
index 0000000..6e43991
--- /dev/null
+++ b/test_scripts/test2.ss
@@ -0,0 +1,12 @@
+<?void
+
+int $num = 123;
+double $double = 12.3;
+string $variable = "This is a string content with a number: 123";
+
+string $variable2 = $variable;
+
+print("$double: ", $double, "\n");
+print("$variable2: ", $variable2, "\n");
+print("$variable: ", $variable, "\n");
+

--
Gitblit v1.9.3