A simple scripting language in C++
Ferenc Szontágh
2025-04-18 4abeb5f8a6ad77b32496f3e8b20e1fd1b6f428fb
function checking
11 files modified
2 files added
150 ■■■■ changed files
.env 1 ●●●● patch | view | raw | blame | history
.gitignore 4 ●●● patch | view | raw | blame | history
.gitsecret/paths/mapping.cfg patch | view | raw | blame | history
src/Interpreter/Interpreter.hpp 11 ●●●● patch | view | raw | blame | history
src/Interpreter/Operation.hpp 7 ●●●●● patch | view | raw | blame | history
src/Interpreter/OperationsFactory.hpp 19 ●●●●● patch | view | raw | blame | history
src/Parser/ParsedExpression.hpp 4 ●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 63 ●●●● patch | view | raw | blame | history
src/Parser/Parser.hpp 21 ●●●● patch | view | raw | blame | history
src/Symbols/SymbolContainer.hpp 6 ●●●●● patch | view | raw | blame | history
src/Symbols/SymbolTable.hpp 4 ●●●● patch | view | raw | blame | history
src/Symbols/Value.hpp 3 ●●●● patch | view | raw | blame | history
src/VoidScript.hpp 7 ●●●●● patch | view | raw | blame | history
.env
New file
@@ -0,0 +1 @@
OPENAI_API_KEY=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
.gitignore
@@ -21,4 +21,6 @@
*.app
.vscode
.cache
build
build
.gitsecret/keys/random_seed
!*.secret
.gitsecret/paths/mapping.cfg
src/Interpreter/Interpreter.hpp
@@ -6,6 +6,7 @@
#include "Interpreter/Operation.hpp"
#include "Interpreter/OperationContainer.hpp"
#include "Symbols/SymbolContainer.hpp"
namespace Interpreter {
@@ -38,7 +39,13 @@
                    break;
                }
            case Operations::Type::FunctionCall:
            case Operations::Type::FunctionCall: {
                // Check that the called function is defined in the symbol table
                if (!Symbols::SymbolContainer::instance()->exists(op.targetName)) {
                    throw std::runtime_error("Function not declared: " + op.targetName);
                }
                break;
            }
            case Operations::Type::Return:
            case Operations::Type::Loop:
            case Operations::Type::Break:
@@ -47,7 +54,7 @@
            case Operations::Type::Import:
            case Operations::Type::Error:
            case Operations::Type::Conditional:
                // TODO: implementálható később
                // TODO: implement these operations later
                break;
            default:
                throw std::runtime_error("Not implemented operation type");
src/Interpreter/Operation.hpp
@@ -6,7 +6,7 @@
#include <string>
#include <unordered_map>
#include "Interpreter/StatementNode.hpp"
#include "StatementNode.hpp"
namespace Operations {
enum class Type : std::uint8_t {
@@ -62,8 +62,9 @@
    }
    std::string toString() const {
        return "Target: " + targetName + " Type: " + this->typeToString() + " Statement: " + statement->toString();
     }
        return "Target: " + targetName + " Type: " + this->typeToString() +
               " Statement: " + ((statement == nullptr) ? "no statement" : statement->toString());
    }
};
};  // namespace Operations
#endif
src/Interpreter/OperationsFactory.hpp
@@ -52,6 +52,25 @@
                                              Operations::Container::instance()->add(
                                                  ns, Operations::Operation{Operations::Type::Declaration, varName, std::move(stmt)});
                                          }
    /**
     * @brief Record a function call operation for later detection.
     * @param functionName Name of the function being called.
     * @param ns Current namespace scope.
     * @param fileName Source filename.
     * @param line Line number of call.
     * @param column Column number of call.
     */
    static void callFunction(const std::string & functionName,
                             const std::string & ns,
                             const std::string & fileName,
                             int line,
                             size_t column) {
        // No associated StatementNode; this is for detection only
        Operations::Container::instance()->add(
            ns,
            Operations::Operation{Operations::Type::FunctionCall, functionName, nullptr});
    }
};
}  // namespace Interpreter
src/Parser/ParsedExpression.hpp
@@ -4,8 +4,8 @@
#include <memory>
#include <string>
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/Value.hpp"
#include "../Symbols/SymbolContainer.hpp"
#include "../Symbols/Value.hpp"
namespace Parser {
src/Parser/Parser.cpp
@@ -1,4 +1,5 @@
#include "Parser/Parser.hpp"
#include <stack>
#include "Interpreter/OperationsFactory.hpp"
@@ -108,6 +109,34 @@
    parseFunctionBody(opening_brace, func_name, func_return_type, param_infos);
}
// Parse a top-level function call, e.g., foo(arg1, arg2);
void Parser::parseCallStatement() {
    // Function name
    auto        id_token  = expect(Lexer::Tokens::Type::IDENTIFIER);
    std::string func_name = id_token.value;
    // Opening parenthesis
    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
    // Parse comma-separated argument expressions
    std::vector<ParsedExpressionPtr> args;
    if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ")")) {
        while (true) {
            // Parse expression with no expected type
            auto expr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
            args.push_back(std::move(expr));
            if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
                continue;
            }
            break;
        }
    }
    // Closing parenthesis and semicolon
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
    // Record the function call operation
    Interpreter::OperationsFactory::callFunction(func_name, Symbols::SymbolContainer::instance()->currentScopeName(),
                                                 this->current_filename_, id_token.line_number, id_token.column_number);
}
Symbols::Value Parser::parseNumericLiteral(const std::string & value, bool is_negative, Symbols::Variables::Type type) {
    try {
        switch (type) {
@@ -175,7 +204,7 @@
    if (startIt != tokens_.end() && endIt != tokens_.end() && startIt < endIt) {
        filtered_tokens = std::vector<Lexer::Tokens::Token>(startIt + 1, endIt);
    }
    auto len = closing_brace.start_pos - opening_brace.end_pos;
    auto             len          = closing_brace.start_pos - opening_brace.end_pos;
    std::string_view input_string = input_str_view_.substr(opening_brace.end_pos, len);
    current_token_index_ = tokenIndex;
@@ -210,7 +239,23 @@
            consumeToken();
            expect_unary = true;
        } else if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == ")") {
            // Only handle grouping parentheses if a matching "(" exists on the operator stack
            std::stack<std::string> temp_stack = operator_stack;
            bool has_paren = false;
            while (!temp_stack.empty()) {
                if (temp_stack.top() == "(") {
                    has_paren = true;
                    break;
                }
                temp_stack.pop();
            }
            if (!has_paren) {
                // End of this expression context; do not consume call-closing parenthesis here
                break;
            }
            // Consume the grouping closing parenthesis
            consumeToken();
            // Unwind operators until the matching "(" is found
            while (!operator_stack.empty() && operator_stack.top() != "(") {
                std::string op = operator_stack.top();
                operator_stack.pop();
@@ -233,11 +278,11 @@
                    output_queue.push_back(Lexer::applyOperator(op, std::move(rhs), std::move(lhs)));
                }
            }
            if (operator_stack.empty() || operator_stack.top() != "(") {
                reportError("Mismatched parentheses");
                Parser::reportError("Mismatched parentheses", token);
            }
            operator_stack.pop();  // remove "("
            // Pop the matching "("
            operator_stack.pop();
            expect_unary = false;
        } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC) {
            std::string op = std::string(token.lexeme);
@@ -254,14 +299,14 @@
                    if (top == "u-" || top == "u+") {
                        if (output_queue.empty()) {
                            reportError("Missing operand for unary operator");
                            Parser::reportError("Missing operand for unary operator", token);
                        }
                        auto rhs = std::move(output_queue.back());
                        output_queue.pop_back();
                        output_queue.push_back(Lexer::applyOperator(top, std::move(rhs)));
                    } else {
                        if (output_queue.size() < 2) {
                            reportError("Malformed expression");
                            Parser::reportError("Malformed expression", token);
                        }
                        auto rhs = std::move(output_queue.back());
                        output_queue.pop_back();
@@ -281,7 +326,7 @@
                   token.type == Lexer::Tokens::Type::KEYWORD ||
                   token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
            if (Lexer::pushOperand(token, expected_var_type, output_queue) == false) {
                reportError("Expected literal or variable");
                Parser::reportError("Invalid type", token, "literal or variable");
            }
            consumeToken();
            expect_unary = false;
@@ -296,12 +341,13 @@
        operator_stack.pop();
        if (op == "(" || op == ")") {
            reportError("Mismatched parentheses");
            Parser::reportError("Mismatched parentheses", tokens_[current_token_index_]);
        }
        if (op == "u-" || op == "u+") {
            if (output_queue.empty()) {
                reportError("Missing operand for unary operator");
                Parser::reportError("Invalid type", tokens_[current_token_index_], "literal or variable");
            }
            auto rhs = std::move(output_queue.back());
            output_queue.pop_back();
@@ -309,6 +355,7 @@
        } else {
            if (output_queue.size() < 2) {
                reportError("Malformed expression");
                Parser::reportError("Mailformed expression", tokens_[current_token_index_]);
            }
            auto rhs = std::move(output_queue.back());
            output_queue.pop_back();
src/Parser/Parser.hpp
@@ -62,7 +62,7 @@
            // Technically we should never reach this if parseScript's loop is correct
            // But it's useful as a safety check
            if (!tokens_.empty() && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE) {
            return tokens_.back();  // return the EOF token
                return tokens_.back();  // return the EOF token
            }
            throw std::runtime_error("Unexpected end of token stream reached.");
        }
@@ -152,13 +152,18 @@
               (current_token_index_ == tokens_.size() - 1 && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE);
    }
    [[noreturn]] void reportError(const std::string & message, const std::string& expected = "") {
    [[noreturn]] void reportError(const std::string & message, const std::string & expected = "") {
        if (current_token_index_ < tokens_.size()) {
            throw Exception(message, expected, tokens_[current_token_index_]);
        }
        int line = tokens_.empty() ? 0 : tokens_.back().line_number;
        int col  = tokens_.empty() ? 0 : tokens_.back().column_number;
        throw Exception(message, expected, line, col);
    }
    [[noreturn]] static void reportError(const std::string & message, const Lexer::Tokens::Token & token,
                                         const std::string & expected = "") {
        throw Exception(message, expected, token);
    }
    // parseStatement (unchanged)
@@ -175,12 +180,20 @@
            parseVariableDefinition();
            return;
        }
        // Function call if identifier followed by '('
        if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
            peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
            parseCallStatement();
            return;
        }
        reportError("Unexpected token at beginning of statement");
    }
    void parseVariableDefinition();
    void parseFunctionDefinition();
    // Parse a top-level function call statement (e.g., foo(arg1, arg2);)
    void parseCallStatement();
    // --- Parsing helper functions ---
@@ -189,7 +202,7 @@
    Symbols::Variables::Type parseType() {
        const auto & token = currentToken();
        // Direct lookup for type keyword
        auto it = Parser::variable_types.find(token.type);
        auto         it    = Parser::variable_types.find(token.type);
        if (it != Parser::variable_types.end()) {
            consumeToken();
            return it->second;
@@ -201,7 +214,7 @@
        Lexer::Tokens::Token token       = currentToken();
        bool                 is_negative = false;
    // Handle unary sign
        // Handle unary sign
        if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC && (token.lexeme == "-" || token.lexeme == "+") &&
            peekToken().type == Lexer::Tokens::Type::NUMBER) {
            is_negative = (token.lexeme == "-");
src/Symbols/SymbolContainer.hpp
@@ -1,6 +1,7 @@
#ifndef SYMBOL_CONTAINER_HPP
#define SYMBOL_CONTAINER_HPP
#include <iostream>
#include <memory>
#include <stdexcept>
#include <unordered_map>
@@ -33,7 +34,8 @@
    }
    void enter(const std::string & name) {
        if (scopes_.contains(name)) {
        auto it = scopes_.find(name);
        if (it != scopes_.end()) {
            previousScope_ = currentScope_;
            currentScope_  = name;
        } else {
@@ -104,7 +106,7 @@
    }
    static std::string dump() {
        std::string result = "";
        std::string result;
        std::cout << "\n--- Defined Scopes ---" << '\n';
        for (const auto & scope_name : instance()->getScopeNames()) {
src/Symbols/SymbolTable.hpp
@@ -2,7 +2,7 @@
#define SYMBOL_TABLE_HPP
#include <vector>
#include <string>
#include "SymbolTypes.hpp"
namespace Symbols {
@@ -45,7 +45,7 @@
    std::vector<SymbolPtr> listAll(const std::string & prefix = "") const {
        std::vector<SymbolPtr> result;
        for (const auto & [ns, map] : symbols_) {
            if (prefix.empty() || ns.starts_with(prefix)) {
            if (prefix.empty() || ns.substr(0,prefix.length()) == prefix) {
                for (const auto & [_, sym] : map) {
                    result.push_back(sym);
                }
src/Symbols/Value.hpp
@@ -2,12 +2,11 @@
#define SYMBOL_VALUE_HPP
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <string>
#include <variant>
#include "Symbols/VariableTypes.hpp"
#include "VariableTypes.hpp"
namespace Symbols {
src/VoidScript.hpp
@@ -54,13 +54,6 @@
                this->lexer->addNamespaceInput(ns, file_content);
                const auto tokens = this->lexer->tokenizeNamespace(ns);
                // dump tokens
                std::cout << "--- Tokens ---\n";
                for (const auto & token : tokens) {
                    token.print();
                }
                std::cout << Operations::Container::dump() << "\n";
                parser->parseScript(tokens, file_content, file);