A simple scripting language in C++
Ferenc Szontágh
2025-04-14 c1a905b5020c4f2f4ade85577e0c36811be87de4
packaging, docs
8 files modified
3 files added
345 ■■■■ changed files
.clangd 5 ●●●● patch | view | raw | blame | history
CMakeLists.txt 156 ●●●●● patch | view | raw | blame | history
LICENSE 21 ●●●●● patch | view | raw | blame | history
README.md 55 ●●●●● patch | view | raw | blame | history
assets/linux/DESCRIPTION.txt 4 ●●●● patch | view | raw | blame | history
cli/main.cpp 49 ●●●●● patch | view | raw | blame | history
cmake/AppVersion.cmake 9 ●●●●● patch | view | raw | blame | history
cmake/options.h.in 19 ●●●●● patch | view | raw | blame | history
src/ScriptInterpreter.cpp 19 ●●●● patch | view | raw | blame | history
src/ScriptInterpreter.hpp 4 ●●●● patch | view | raw | blame | history
src/ScriptInterpreterHelpers.hpp 4 ●●●● patch | view | raw | blame | history
.clangd
@@ -1,2 +1,5 @@
CompileFlags:
  Add: [-Wunused]
  Add: [-Wunused]
Index:
  StandardLibrary: No
CMakeLists.txt
@@ -1,100 +1,150 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.20)
project(
    voidscript
    LANGUAGES CXX
    VERSION 0.0.1
    )
    DESCRIPTION "A simple scripting language"
    HOMEPAGE_URL "https://github.com/fszontagh/voidshell"
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(NEED_CLI ON)
set(NEED_TESTS OFF)
set(NEED_STATIC_LIBS ON)
set(NEED_SHARED_LIBS ON)
option(BUILD_CLI, "Build example commandline intrepeter" ${NEED_CLI})
option(BUILD_TESTS, "Build tests" ${NEED_TESTS})
option(BUILD_SHARED_LIBS "Build shared library" ${NEED_SHARED_LIBS})
option(BUILD_STATIC_LIBS "Build static library" ${NEED_STATIC_LIBS})
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
if (BUILD_CLI)
    set(NEED_CLI ${BUILD_CLI})
endif()
if (BUILD_TESTS)
    set(NEED_TESTS ${BUILD_TESTS})
endif()
if (BUILD_SHARED_LIBS)
    set(NEED_SHARED_LIBS ${BUILD_SHARED_LIBS})
endif()
if (BUILD_STATIC_LIBS)
    set(NEED_STATIC_LIBS ${BUILD_STATIC_LIBS})
endif()
include(cmake/AppVersion.cmake)
set(COMMENT_CHARACTER "#")
set(PARSER_OPEN_TAG "<?void")
set(PARSER_CLOSE_TAG "?>")
message(STATUS "BUILD_CLI:           ${NEED_CLI}")
message(STATUS "BUILD_TESTS:         ${NEED_TESTS}")
message(STATUS "BUILD_SHARED_LIBS:   ${NEED_SHARED_LIBS}")
message(STATUS "BUILD_STATIC_LIBS:   ${NEED_STATIC_LIBS}")
message(STATUS "BUILD_SHARED_LIBS:   ${BUILD_SHARED_LIBS}")
message(STATUS "  COMMENT_CHARACTER: ${COMMENT_CHARACTER}")
message(STATUS "  PARSER_OPEN_TAG:   ${PARSER_OPEN_TAG}")
message(STATUS "  PARSER_CLOSE_TAG:  ${PARSER_CLOSE_TAG}")
message(STATUS "APP_GIT_VERSION:     ${APP_GIT_VERSION}")
if (CMAKE_BUILD_TYPE STREQUAL "")
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
    set(DEBUG_BUILD ON)
    set(COMPILE_WARNING_AS_ERROR ON)
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()
# PACKAGING PROPERTIES
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/assets/linux/DESCRIPTION.txt")
set(CPACK_PACKAGE_CONTACT "Ferenc Szontágh <szf@fsociety.hu>")
set(CPACK_PACKAGE_VENDOR "fszontagh")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/fszontagh/voidscript")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Voidscript a simple scripting language")
set(CPACK_PACKAGE_CHECKSUM "SHA512")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
#set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
set(CPACK_STRIP_FILES YES)
set(
    CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
    OWNER_READ OWNER_WRITE OWNER_EXECUTE
    GROUP_READ GROUP_EXECUTE
    WORLD_READ WORLD_EXECUTE
)
set(CPACK_PACKAGING_INSTALL_PREFIX "/")
set(ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
    execute_process(
        COMMAND bash -c "source /etc/lsb-release && echo \"$DISTRIB_CODENAME\""
        OUTPUT_VARIABLE DISTRIB_CODENAME
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    set(CPACK_SYSTEM_NAME ${DISTRIB_CODENAME})
    message(STATUS "Detected DISTRIB_CODENAME: ${DISTRIB_CODENAME}")
    find_program(DPKG dpkg)
    if (DPKG)
        execute_process(COMMAND ${DPKG} --print-architecture OUTPUT_VARIABLE ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
    endif()
    if(CMAKE_SIZEOF_VOID_P EQUAL 8)
        set(ARCHITECTURE "amd64")
    elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
        set(ARCHITECTURE "i386")
    else()
        message(STATUS "Célarchitektúra: Nem meghatározható (CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P})")
    endif()
endif()
message(STATUS "System architecture: ${ARCHITECTURE}")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "/")
endif()
include(GNUInstallDirs)
# PACKAGING PROPERTIES END
configure_file("cmake/options.h.in" "include/options.h" @ONLY)
configure_file("test_scripts/test1.vs" "test_scripts/test1.vs" @ONLY)
configure_file("test_scripts/test2.vs" "test_scripts/test2.vs" @ONLY)
include_directories(${CMAKE_BINARY_DIR}/include src)
include_directories(${CMAKE_BINARY_DIR}/include)
include_directories(src)
file(GLOB_RECURSE SOURCES
    src/main.cpp
    src/ScriptInterpreter.cpp
    src/Lexer.cpp
# LIBRARY TARGET
add_library(voidscript
            src/ScriptInterpreter.cpp
            src/Lexer.cpp
)
if (NEED_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 (NEED_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()
install(TARGETS voidscript DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT "lib")
set(CPACK_DEBIAN_LIB_FILE_NAME "libvoidscript_${CMAKE_PROJECT_VERSION}_${ARCHITECTURE}_${CPACK_SYSTEM_NAME}.deb")
set(CPACK_DEBIAN_LIB_PACKAGE_NAME "libvoidscript")
set(CPACK_DEBIAN_LIB_PACKAGE_SECTION "libs")
set_target_properties(voidscript PROPERTIES VERSION ${CMAKE_PROJECT_VERSION} SOVERSION 1)
message(STATUS "CMAKE_SYSTEM: ${CMAKE_SYSTEM}")
set_target_properties(voidscript PROPERTIES
    LINKER_LANGUAGE CXX
    LIBRARY_OUTPUT_NAME voidscript
)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
# EXECUTABLE TARGET
if (NEED_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()
    add_executable(voidscript-cli cli/main.cpp)
    target_link_libraries(voidscript-cli voidscript)
    install(TARGETS voidscript-cli DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "bin")
    set_target_properties(voidscript-cli PROPERTIES
    LINKER_LANGUAGE CXX
    OUTPUT_NAME voidscript)
    set(CPACK_PACKAGE_EXECUTABLES voidscript-cli "Simple Shell")
    set(CPACK_DEBIAN_BIN_FILE_NAME "voidscript_${CMAKE_PROJECT_VERSION}_${ARCHITECTURE}_${CPACK_SYSTEM_NAME}.deb")
    set(CPACK_DEBIAN_BIN_PACKAGE_NAME "voidscript")
    set(CPACK_DEBIAN_BIN_PACKAGE_SECTION "interpreters")
    set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS ${CMAKE_BINARY_DIR})
    set(CPACK_DEBIAN_BIN_PACKAGE_DEPENDS "libvoidscript (= ${CMAKE_PROJECT_VERSION})")
endif()
# CPACK CONFIGURATION
set(CPACK_DEB_COMPONENT_INSTALL ON)
include(CPack)
LICENSE
New file
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 fszontagh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
README.md
@@ -1,4 +1,55 @@
## soniscript
## voidscript
A simple scripting language in C++
A lightweight scripting language designed for simplicity and ease of use.
### Features
- Simple and intuitive syntax
- Built-in standard library
- Cross-platform compatibility
- Easy to embed in other applications
- No external dependencies
### Installation
```bash
git clone https://github.com/fszontagh/voidscript.git
cd voidscript
cmake -S . -B build
cmake --build build
```
### Quick Start
```voidscript
# Hello World example
print("Hello,", "", "World!", "\n");
# Variables
int x = 42;
string name = "VoidScript";
// Functions
function $add(int $a, double $b, string $help) {
    int $result = $a + $b;
    print("The sum is: ", $result, "\n");
    print("Help: ", $help, "\n");
    return $result;
}
```
### Documentation
For detailed documentation, please visit the [Wiki](https://github.com/fszontagh/voidscript/wiki).
### Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
### License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
assets/linux/DESCRIPTION.txt
New file
@@ -0,0 +1,4 @@
A simple scripting language in C++
voidscript is a lightweight scripting language implementation written in C++.
It provides a simple syntax for writing scripts and can be embedded in other applications.
cli/main.cpp
@@ -5,35 +5,48 @@
#include "Builtins/SleepModule.hpp"
#include "ScriptInterpreter.hpp"
static bool DEBUG = false;
const std::unordered_map<std::string, std::string> params = {
    { "--help",    "Print this help message"          },
    { "--version", "Print the version of the program" },
};
int main(int argc, char * argv[]) {
    std::string usage = "Usage: " + std::string(argv[0]);
    for (const auto & [key, value] : params) {
        usage.append(" [" + key + "]");
    }
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
        std::cerr << usage << "\n";
        return 1;
    }
    std::string file;
    if (argc == 2) {
        file = argv[1];
    } else if (argc == 3) {
        if (std::string(argv[1]) == "-d" || std::string(argv[1]) == "--debug") {
            DEBUG = true;
            file  = argv[2];
        } else if (argv[1] == "-h" || argv[1] == "--help") {
            std::cout << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
    const std::string arg = std::string(argv[1]);
    if (arg.starts_with("-")) {
        auto it = params.find(arg);
        if (it != params.end()) {
            if (arg == "--help") {
                std::cout << usage << "\n";
                for (const auto & [key, value] : params) {
                    std::cout << "  " << key << ": " << value << "\n";
                }
                return 0;
            }
            if (arg == "--version") {
                std::cout << "Version:      " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH;
                std::cout << " (" << VERSION_GIT_HASH << ")\n";
                std::cout << "Architecture: " << VERSION_ARCH << "\n";
                std::cout << "System:       " << VERSION_SYSTEM_NAME << "\n";
                return 0;
            }
            return 0;
        } else if (argv[1] == "-v" || argv[1] == "--vrsion") {
            std::cout << "VoidScript v" << VERSION_STRING << "\n";
            return 0;
        } else {
            std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
            return 1;
        }
    } else {
        std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
        std::cerr << "Error: Unknown option " << arg << "\n";
        std::cerr << usage << "\n";
        return 1;
    }
    file = arg;
    if (!std::filesystem::exists(file)) {
        std::cerr << "Error: File " << file << " does not exist.\n";
cmake/AppVersion.cmake
New file
@@ -0,0 +1,9 @@
find_package(Git QUIET REQUIRED)
execute_process(
    COMMAND "${GIT_EXECUTABLE}" describe --always HEAD
    WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
    RESULT_VARIABLE res
    OUTPUT_VARIABLE APP_GIT_VERSION
    ERROR_QUIET
    OUTPUT_STRIP_TRAILING_WHITESPACE)
cmake/options.h.in
@@ -10,13 +10,16 @@
#else
const char EOL = '\n';
#endif
const char   COMMENT_CHARACTER = '@COMMENT_CHARACTER@';
const static char * PARSER_OPEN_TAG   = "@PARSER_OPEN_TAG@";
const static char * PARSER_CLOSE_TAG  = "@PARSER_CLOSE_TAG@";
const static char * VERSION_MINOR     = "@CMAKE_PROJECT_VERSION_MINOR@";
const static char * VERSION_MAJOR     = "@CMAKE_PROJECT_VERSION_MAJOR@";
const static char * VERSION_PATCH     = "@CMAKE_PROJECT_VERSION_PATCH@";
const static char * VERSION_STRING    = "@CMAKE_PROJECT_VERSION@-@CMAKE_BUILD_TYPE@-@CMAKE_SYSTEM@";
const char          COMMENT_CHARACTER   = '@COMMENT_CHARACTER@';
const static char * PARSER_OPEN_TAG     = "@PARSER_OPEN_TAG@";
const static char * PARSER_CLOSE_TAG    = "@PARSER_CLOSE_TAG@";
const static char * VERSION_MINOR       = "@CMAKE_PROJECT_VERSION_MINOR@";
const static char * VERSION_MAJOR       = "@CMAKE_PROJECT_VERSION_MAJOR@";
const static char * VERSION_PATCH       = "@CMAKE_PROJECT_VERSION_PATCH@";
const static char * VERSION_ARCH        = "@ARCHITECTURE@";
const static char * VERSION_SYSTEM_NAME = "@CMAKE_SYSTEM_NAME@";
const static char * VERSION_GIT_HASH    = "@APP_GIT_VERSION@";
const static char * VERSION_STRING      = "v@CMAKE_PROJECT_VERSION@-@CMAKE_SYSTEM_NAME@-@ARCHITECTURE@-@CMAKE_BUILD_TYPE@";
#cmakedefine01 DEBUG_BUILD
#endif // VOIDSCRIPT_OPTIONS_H
#endif  // VOIDSCRIPT_OPTIONS_H
src/ScriptInterpreter.cpp
@@ -140,7 +140,7 @@
};
void ScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
    const auto varToken = tokens[i];
    const auto & varToken = tokens[i];
    i++;      // Skip variable name
    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
@@ -211,7 +211,6 @@
void ScriptInterpreter::handleFunctionDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
    i++;  // skip funct name
@@ -231,15 +230,15 @@
    // parse arg definitions
    const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i, __FILE__, __LINE__);
    std::cout << "args: " << args.size() << std::endl;
    std::cout << "args: " << args.size() << '\n';
    for (const auto & arg : args) {
        std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << std::endl;
        std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << '\n';
    }
    this->functionParameters[varName].assign(args.begin(), args.end());
    size_t start;
    size_t end;
    ScriptInterpreterHelpers::getFunctionBody(tokens, i, start, end);
    std::cout << "Body start:  " << start << " end: " << end << std::endl;
    std::cout << "Body start:  " << start << " end: " << end << '\n';
    const std::string function_body = ScriptInterpreterHelpers::extractSubstring(this->source, start, end);
    this->functionBodies[varName]   = function_body;
    // recheck the close curly brace
@@ -248,7 +247,7 @@
    }
    i++;
#if DEBUG_BUILD == 1
    std::cout << "function body: \n\"" << function_body << "\"" << std::endl;
    std::cout << "function body: \n\"" << function_body << "\"" << '\n';
#endif
}
@@ -295,14 +294,6 @@
    } else {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "'=' for assignment");
    }
}
void ScriptInterpreter::handleComment(std::size_t & i) {
    i++;  // Skip comment token
}
void ScriptInterpreter::handleSemicolon(std::size_t & i) {
    i++;  // Skip semicolon token
}
void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename,
src/ScriptInterpreter.hpp
@@ -110,8 +110,8 @@
    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);
    static void handleComment(std::size_t & i){ i++;}
    static void handleSemicolon(std::size_t & i) {i++;};
    void handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i);
    void handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i);
    void handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type);
src/ScriptInterpreterHelpers.hpp
@@ -75,7 +75,7 @@
                            std::size_t & end) {
    start = tokens[i].pos.end;
    std::cout << "START Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
              << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
              << " end pos: " << std::to_string(tokens[i].pos.end) << '\n';
    if (i >= tokens.size() || tokens[i].type != TokenType::LeftCurlyBracket) {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "{");
@@ -96,7 +96,7 @@
    end = tokens[i].pos.start - 1;
    std::cout << "END Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
              << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
              << " end pos: " << std::to_string(tokens[i].pos.end) << '\n';
    if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}");