A simple scripting language in C++
Ferenc Szontágh
2025-04-12 0489092ac538610a3db7dee2e000bc63db11be67
rename the interpreter
6 files modified
2 files renamed
131 ■■■■ changed files
CMakeLists.txt 11 ●●●● patch | view | raw | blame | history
cli/main.cpp 52 ●●●● patch | view | raw | blame | history
cmake/options.h.in 6 ●●●● patch | view | raw | blame | history
src/BaseFunction.hpp 4 ●●●● patch | view | raw | blame | history
src/Builtins/PrintModule.hpp 2 ●●● patch | view | raw | blame | history
src/ScriptExceptionMacros.h 8 ●●●● patch | view | raw | blame | history
src/ScriptInterpreter.cpp 24 ●●●● patch | view | raw | blame | history
src/ScriptInterpreter.hpp 24 ●●●● patch | view | raw | blame | history
CMakeLists.txt
@@ -1,5 +1,9 @@
cmake_minimum_required(VERSION 3.16)
project(sonyscript LANGUAGES CXX)
cmake_minimum_required(VERSION 3.5)
project(
    voidscript
    LANGUAGES CXX
    VERSION 0.0.1
    )
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -27,6 +31,7 @@
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)
endif()
configure_file("cmake/options.h.in" "include/options.h" @ONLY)
@@ -36,7 +41,7 @@
file(GLOB_RECURSE SOURCES
    src/main.cpp
    src/SScriptInterpreter.cpp
    src/ScriptInterpreter.cpp
    src/Lexer.cpp
)
cli/main.cpp
@@ -2,51 +2,47 @@
#include <fstream>
#include "Builtins/PrintModule.hpp"
#include "SScriptInterpreter.hpp"
#include "ScriptInterpreter.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;
        std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
        return 1;
    }
    if (!std::filesystem::exists(argv[2])) {
        std::cerr << "Error: File " << argv[2] << " does not exist." << std::endl;
    std::string file;
    if (argc == 2) {
        file = argv[1];
    } else if (argc == 3 && (std::string(argv[1]) == "-d" || std::string(argv[1]) == "--debug")) {
        DEBUG = true;
        file  = argv[2];
    } else {
        std::cerr << "Usage: " << argv[0] << " [-d / --debug] <script_file>\n";
        return 1;
    }
    // get the absolute path of the file
    const std::string filename = std::filesystem::canonical(argv[2]).string();
    if (!std::filesystem::exists(file)) {
        std::cerr << "Error: File " << file << " does not exist.\n";
        return 1;
    }
    const std::string filename = std::filesystem::canonical(file).string();
    try {
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Error: Could not open file " << filename << std::endl;
        std::ifstream input(filename);
        if (!input.is_open()) {
            std::cerr << "Error: Could not open file " << filename << "\n";
            return 1;
        }
        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        std::string        content((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
        ScriptInterpreter interp;
        interp.registerFunction("print", std::make_shared<PrintFunction>());
        interp.executeScript(content, filename, DEBUG);
    } catch (const std::exception & e) {
        std::cerr << "Parser error: " << e.what() << std::endl;
        std::cerr << "Parser error: " << e.what() << "\n";
        return 1;
    }
cmake/options.h.in
@@ -9,4 +9,8 @@
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 @
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@";
#cmakedefine DEBUG_BUILD
src/BaseFunction.hpp
@@ -8,7 +8,7 @@
#include "Value.hpp"
class SScriptInterpreter;
class ScriptInterpreter;
class BaseFunction {
  protected:
@@ -18,7 +18,7 @@
    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) {
    template <typename FuncClass> void registerFunctionTo(ScriptInterpreter & interp) {
        FuncClass::registerTo(interp);
    }
};
src/Builtins/PrintModule.hpp
@@ -5,7 +5,7 @@
#include "BaseFunction.hpp"
#include "ScriptExceptionMacros.h"
#include "SScriptInterpreter.hpp"
#include "ScriptInterpreter.hpp"
#include "Token.hpp"
#include "Value.hpp"
src/ScriptExceptionMacros.h
@@ -2,18 +2,18 @@
#define SCRIPT_EXCEPTION_MACROS_H
#define THROW_UNEXPECTED_TOKEN_ERROR(token, expected) \
    SScriptInterpreter::throwUnexpectedTokenError(token, expected, __FILE__, __LINE__)
    ScriptInterpreter::throwUnexpectedTokenError(token, expected, __FILE__, __LINE__)
#define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \
    SScriptInterpreter::throwUndefinedVariableError(name, token, __FILE__, __LINE__)
    ScriptInterpreter::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,                  \
    ScriptInterpreter::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__)
    ScriptInterpreter::throwVariableRedefinitionError(name, token, __FILE__, __LINE__)
#endif  // SCRIPT_EXCEPTION_MACROS_H
src/ScriptInterpreter.cpp
File was renamed from src/SScriptInterpreter.cpp
@@ -1,4 +1,4 @@
#include "SScriptInterpreter.hpp"
#include "ScriptInterpreter.hpp"
#include <iostream>
#include <stdexcept>
@@ -9,11 +9,11 @@
#include "ScriptExceptionMacros.h"
#include "Value.hpp"
void SScriptInterpreter::registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn) {
void ScriptInterpreter::registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn) {
    functionObjects[name] = std::move(fn);
}
Value SScriptInterpreter::evaluateExpression(const Token & token) const {
Value ScriptInterpreter::evaluateExpression(const Token & token) const {
    if (token.type == TokenType::StringLiteral) {
        return Value::fromString(token.lexeme);
    }
@@ -46,7 +46,7 @@
    return Value();
}
std::vector<Value> SScriptInterpreter::parseArguments(const std::vector<Token> & tokens,
std::vector<Value> ScriptInterpreter::parseArguments(const std::vector<Token> & tokens,
                                                      std::size_t &              current_index) const {
    std::vector<Value> args;
@@ -82,7 +82,7 @@
    return args;
}
void SScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
void ScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
@@ -116,7 +116,7 @@
    }
}
void SScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type) {
void ScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
@@ -162,7 +162,7 @@
    }
}
void SScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) {
void ScriptInterpreter::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()) {
@@ -176,7 +176,7 @@
    }
}
void SScriptInterpreter::handleVariableReference(const std::vector<Token> & tokens, std::size_t & i) {
void ScriptInterpreter::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;
@@ -198,15 +198,15 @@
    }
}
void SScriptInterpreter::handleComment(std::size_t & i) {
void ScriptInterpreter::handleComment(std::size_t & i) {
    i++;  // Skip comment token
}
void SScriptInterpreter::handleSemicolon(std::size_t & i) {
void ScriptInterpreter::handleSemicolon(std::size_t & i) {
    i++;  // Skip semicolon token
}
void SScriptInterpreter::expectSemicolon(const std::vector<Token> & tokens, std::size_t & i,
void ScriptInterpreter::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);
@@ -215,7 +215,7 @@
    }
}
void SScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool debug) {
void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool debug) {
    Lexer lexer(source, filename);
    auto  tokens = lexer.tokenize();
src/ScriptInterpreter.hpp
File was renamed from src/SScriptInterpreter.hpp
@@ -12,7 +12,7 @@
using FunctionValidator = std::function<void(const std::vector<Token> &, size_t &)>;
class SScriptInterpreter {
class ScriptInterpreter {
  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);
@@ -23,10 +23,10 @@
            "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
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string error_message = error_content;
        const std::string& error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    };
@@ -35,19 +35,19 @@
                                            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
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string error_message = error_content;
        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) {
                                                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()) {
@@ -59,7 +59,7 @@
        error_content += " in file: " + token.file + ":" + std::to_string(token.lineNumber) + ":" +
                         std::to_string(token.columnNumber);
#if BUILD_TYPE == Debug
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string error_message = error_content;
@@ -71,10 +71,10 @@
                                               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
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string error_message = error_content;
        const std::string& error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    }