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