A simple scripting language in C++
Ferenc Szontágh
2025-04-12 7d7a1e80c8a8c1e52446453d1b86d3c3b945ec29
init
1 files modified
17 files added
1445 ■■■■■ changed files
.clang-format 160 ●●●●● patch | view | raw | blame | history
.clang-tidy 26 ●●●●● patch | view | raw | blame | history
.gitignore 3 ●●●●● patch | view | raw | blame | history
CMakeLists.txt 65 ●●●●● patch | view | raw | blame | history
cli/main.cpp 54 ●●●●● patch | view | raw | blame | history
cmake/options.h.in 12 ●●●●● patch | view | raw | blame | history
src/BaseFunction.hpp 26 ●●●●● patch | view | raw | blame | history
src/Builtins/PrintModule.hpp 61 ●●●●● patch | view | raw | blame | history
src/Lexer.cpp 246 ●●●●● patch | view | raw | blame | history
src/Lexer.hpp 50 ●●●●● patch | view | raw | blame | history
src/SScriptInterpreter.cpp 275 ●●●●● patch | view | raw | blame | history
src/SScriptInterpreter.hpp 99 ●●●●● patch | view | raw | blame | history
src/ScriptExceptionMacros.h 19 ●●●●● patch | view | raw | blame | history
src/Token.hpp 92 ●●●●● patch | view | raw | blame | history
src/Value.hpp 168 ●●●●● patch | view | raw | blame | history
src/VariableTypes.hpp 63 ●●●●● patch | view | raw | blame | history
test_scripts/test1.ss 14 ●●●●● patch | view | raw | blame | history
test_scripts/test2.ss 12 ●●●●● patch | view | raw | blame | history
.clang-format
New file
@@ -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']
...
.clang-tidy
New file
@@ -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
.gitignore
@@ -19,3 +19,6 @@
*.exe
*.out
*.app
.vscode
.cache
build
CMakeLists.txt
New file
@@ -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()
cli/main.cpp
New file
@@ -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;
}
cmake/options.h.in
New file
@@ -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 @
src/BaseFunction.hpp
New file
@@ -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
src/Builtins/PrintModule.hpp
New file
@@ -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
src/Lexer.cpp
New file
@@ -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;
}
src/Lexer.hpp
New file
@@ -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
src/SScriptInterpreter.cpp
New file
@@ -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);
        }
    }
}
src/SScriptInterpreter.hpp
New file
@@ -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
src/ScriptExceptionMacros.h
New file
@@ -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
src/Token.hpp
New file
@@ -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
src/Value.hpp
New file
@@ -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
src/VariableTypes.hpp
New file
@@ -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
test_scripts/test1.ss
New file
@@ -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);
?>
test_scripts/test2.ss
New file
@@ -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");