A simple scripting language in C++
Ferenc Szontágh
2025-04-18 acb1c33ab258dc14dcc51b405b44cc5923b34324
add function call
6 files modified
1 files added
1 files deleted
319 ■■■■ changed files
src/!NameSpaceManager.hpp 144 ●●●●● patch | view | raw | blame | history
src/Interpreter/CallStatementNode.hpp 90 ●●●●● patch | view | raw | blame | history
src/Interpreter/Interpreter.hpp 33 ●●●●● patch | view | raw | blame | history
src/Interpreter/OperationsFactory.hpp 18 ●●●● patch | view | raw | blame | history
src/Lexer/Operators.hpp 12 ●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 9 ●●●● patch | view | raw | blame | history
src/Symbols/SymbolFactory.hpp 5 ●●●●● patch | view | raw | blame | history
test_scripts/test2.vs 8 ●●●●● patch | view | raw | blame | history
src/!NameSpaceManager.hpp
File was deleted
src/Interpreter/CallStatementNode.hpp
New file
@@ -0,0 +1,90 @@
#ifndef INTERPRETER_CALL_STATEMENT_NODE_HPP
#define INTERPRETER_CALL_STATEMENT_NODE_HPP
#include <memory>
#include <string>
#include <vector>
#include "ExpressionNode.hpp"
#include "Interpreter/Interpreter.hpp"
#include "Interpreter/OperationContainer.hpp"
#include "StatementNode.hpp"
#include "Symbols/FunctionSymbol.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/SymbolFactory.hpp"
#include "Symbols/Value.hpp"
namespace Interpreter {
/**
 * @brief Statement node representing a function call with argument expressions.
 */
class CallStatementNode : public StatementNode {
    std::string                                  functionName_;
    std::vector<std::unique_ptr<ExpressionNode>> args_;
  public:
    CallStatementNode(const std::string & functionName, std::vector<std::unique_ptr<ExpressionNode>> args,
                      const std::string & file_name, int file_line, size_t column) :
        StatementNode(file_name, file_line, column),
        functionName_(functionName),
        args_(std::move(args)) {}
    void interpret(Interpreter & interpreter) const override {
        using namespace Symbols;
        // Evaluate argument expressions
        std::vector<Value> argValues;
        argValues.reserve(args_.size());
        for (const auto & expr : args_) {
            argValues.push_back(expr->evaluate(interpreter));
        }
        // Lookup function symbol in functions namespace
        SymbolContainer * sc        = SymbolContainer::instance();
        const std::string currentNs = sc->currentScopeName();
        const std::string fnSymNs   = currentNs + ".functions";
        auto              sym       = sc->get(fnSymNs, functionName_);
        if (!sym || sym->getKind() != Kind::Function) {
            throw std::runtime_error("Function not found: " + functionName_);
        }
        auto funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
        // Check parameter count
        const auto & params = funcSym->parameters();
        if (params.size() != argValues.size()) {
            throw std::runtime_error("Function '" + functionName_ + "' expects " + std::to_string(params.size()) +
                                     " args, got " + std::to_string(argValues.size()));
        }
        // Enter function scope to bind parameters and execute body
        const std::string fnOpNs = currentNs + "." + functionName_;
        sc->enter(fnOpNs);
        // Bind parameters as local variables
        for (size_t i = 0; i < params.size(); ++i) {
            const auto &  p = params[i];
            const Value & v = argValues[i];
            auto varSym = SymbolFactory::createVariable(p.name, v, fnOpNs);
            sc->add(varSym);
        }
        // Execute function body operations
        auto ops = Operations::Container::instance()->getAll(fnOpNs);
        auto it  = ops.begin();
        for (; it != ops.end(); ++it) {
            interpreter.runOperation(*(*it));
        }
        // Exit function scope
        sc->enterPreviousScope();
    }
    std::string toString() const override {
        return "CallStatementNode{ functionName='" + functionName_ + "', " +
               "args=" + std::to_string(args_.size()) + " " + "filename='" + filename_ + "', " +
               "line=" + std::to_string(line_) + ", " + "column=" + std::to_string(column_) + "}";
    };
};
}  // namespace Interpreter
// namespace Interpreter
#endif  // INTERPRETER_CALL_STATEMENT_NODE_HPP
src/Interpreter/Interpreter.hpp
@@ -12,6 +12,22 @@
class Interpreter {
  private:
  public:
    Interpreter() {}
    /**
     * @brief Execute all operations in the current namespace (e.g., file-level or function-level).
     */
    void run() {
        // Determine namespace to execute
        const std::string ns = Symbols::SymbolContainer::instance()->currentScopeName();
        for (const auto & operation : Operations::Container::instance()->getAll(ns)) {
            runOperation(*operation);
        }
    }
    void runOperation(const Operations::Operation & op) {
        std::cout << "Operation: " << op.toString() << "\n";
@@ -39,13 +55,11 @@
                    break;
                }
            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);
            case Operations::Type::FunctionCall:
                if (op.statement) {
                    op.statement->interpret(*this);
                }
                break;
            }
            case Operations::Type::Return:
            case Operations::Type::Loop:
            case Operations::Type::Break:
@@ -58,15 +72,6 @@
                break;
            default:
                throw std::runtime_error("Not implemented operation type");
        }
    }
  public:
    Interpreter() {}
    void run() {
        for (const auto & operation : Operations::Container::instance()->getAll()) {
            runOperation(*operation);
        }
    }
src/Interpreter/OperationsFactory.hpp
@@ -8,6 +8,7 @@
#include "Interpreter/DeclareVariableStatementNode.hpp"
#include "Interpreter/ExpressionBuilder.hpp"
#include "Interpreter/LiteralExpressionNode.hpp"
#include "Interpreter/CallStatementNode.hpp"
#include "Interpreter/Operation.hpp"
#include "Interpreter/OperationContainer.hpp"
#include "Parser/ParsedExpression.hpp"
@@ -54,22 +55,31 @@
                                          }
    
    /**
     * @brief Record a function call operation for later detection.
     * @brief Record a function call operation with argument expressions.
     * @param functionName Name of the function being called.
     * @param ns Current namespace scope.
     * @param parsedArgs Vector of parsed argument expressions.
     * @param ns Current namespace scope for operations.
     * @param fileName Source filename.
     * @param line Line number of call.
     * @param column Column number of call.
     */
    static void callFunction(const std::string & functionName,
                             std::vector<Parser::ParsedExpressionPtr> &&parsedArgs,
                             const std::string & ns,
                             const std::string & fileName,
                             int line,
                             size_t column) {
        // No associated StatementNode; this is for detection only
        // Build argument ExpressionNode list
        std::vector<std::unique_ptr<ExpressionNode>> exprs;
        exprs.reserve(parsedArgs.size());
        for (auto &pexpr : parsedArgs) {
            exprs.push_back(buildExpressionFromParsed(pexpr));
        }
        // Create call statement node
        auto stmt = std::make_unique<CallStatementNode>(functionName, std::move(exprs), fileName, line, column);
        Operations::Container::instance()->add(
            ns,
            Operations::Operation{Operations::Type::FunctionCall, functionName, nullptr});
            Operations::Operation{Operations::Type::FunctionCall, functionName, std::move(stmt)});
    }
};
src/Lexer/Operators.hpp
@@ -59,8 +59,16 @@
                                      std::vector<Parser::ParsedExpressionPtr> & output_queue) {
    if (token.type == Tokens::Type::NUMBER || token.type == Tokens::Type::STRING_LITERAL ||
        token.type == Tokens::Type::KEYWORD) {
        output_queue.push_back(
            Parser::ParsedExpression::makeLiteral(Symbols::Value::fromString(token.value, expected_var_type)));
        // Parse literal: use expected type if provided, otherwise auto-detect
        if (expected_var_type == Symbols::Variables::Type::NULL_TYPE) {
            output_queue.push_back(
                Parser::ParsedExpression::makeLiteral(
                    Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
        } else {
            output_queue.push_back(
                Parser::ParsedExpression::makeLiteral(
                    Symbols::Value::fromString(token.value, expected_var_type)));
        }
        return true;
    }
    if (token.type == Tokens::Type::VARIABLE_IDENTIFIER) {
src/Parser/Parser.cpp
@@ -133,8 +133,13 @@
    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);
    Interpreter::OperationsFactory::callFunction(
        func_name,
        std::move(args),
        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) {
src/Symbols/SymbolFactory.hpp
@@ -14,6 +14,11 @@
class SymbolFactory {
  public:
    static std::shared_ptr<Symbol> createVariable(const std::string & name, const Symbols::Value & value,
                                                  const std::string & context) {
        return std::make_shared<VariableSymbol>(name, value, context, value.getType());
    }
    static std::shared_ptr<Symbol> createVariable(const std::string & name, const Symbols::Value & value,
                                                  const std::string & context, Variables::Type type) {
        return std::make_shared<VariableSymbol>(name, value, context, type);
    }
test_scripts/test2.vs
@@ -4,7 +4,9 @@
string $variable2 = $variable;
print("$double: ", $double, "\n");
print("$variable2: ", $variable2, "\n");
print("$variable: ", $variable, "\n");
function test = (int $i) {
    int $result = $i + 1;
}
test(5);