1 files modified
17 files added
| New file |
| | |
| | | --- |
| | | 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'] |
| | | ... |
| New file |
| | |
| | | --- |
| | | 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 |
| | |
| | | *.exe |
| | | *.out |
| | | *.app |
| | | .vscode |
| | | .cache |
| | | build |
| New file |
| | |
| | | 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() |
| New file |
| | |
| | | #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; |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 @ |
| New file |
| | |
| | | // 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 |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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; |
| | | } |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | <?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); |
| | | |
| | | ?> |
| New file |
| | |
| | | <?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"); |
| | | |