A simple scripting language in C++
Ferenc Szontágh
2025-04-18 fb8d8f9f5bb4a1f7736d927a346d4bf834a28ffa
add if else statements
5 files modified
7 files added
266 ■■■■■ changed files
src/Interpreter/BinaryExpressionNode.hpp 7 ●●●● patch | view | raw | blame | history
src/Interpreter/ConditionalStatementNode.hpp 57 ●●●●● patch | view | raw | blame | history
src/Interpreter/Interpreter.hpp 7 ●●●● patch | view | raw | blame | history
src/Modules/TypeofModule.hpp 45 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 97 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.hpp 10 ●●●●● patch | view | raw | blame | history
src/VoidScript.hpp 3 ●●●●● patch | view | raw | blame | history
test_scripts/arguments.vs 4 ●●●● patch | view | raw | blame | history
test_scripts/if_statements.vs 21 ●●●●● patch | view | raw | blame | history
test_scripts/object.vs 4 ●●●● patch | view | raw | blame | history
test_scripts/typeof.vs 7 ●●●●● patch | view | raw | blame | history
tmp.vs 4 ●●●● patch | view | raw | blame | history
src/Interpreter/BinaryExpressionNode.hpp
@@ -94,7 +94,12 @@
            if (op_ == "+") {
                return Symbols::Value(l + r);
            }
            if (op_ == "==") {
                return Symbols::Value(l == r);
            }
            if (op_ == "!=") {
                return Symbols::Value(l != r);
            }
            throw std::runtime_error("Unknown operator: " + op_);
        }
src/Interpreter/ConditionalStatementNode.hpp
New file
@@ -0,0 +1,57 @@
 #ifndef INTERPRETER_CONDITIONAL_STATEMENT_NODE_HPP
 #define INTERPRETER_CONDITIONAL_STATEMENT_NODE_HPP
 #include <vector>
 #include <memory>
 #include <string>
 #include "Interpreter/StatementNode.hpp"
 #include "Interpreter/ExpressionNode.hpp"
 namespace Interpreter {
 /**
  * @brief Statement node representing an if-else conditional block.
  */
 class ConditionalStatementNode : public StatementNode {
     std::unique_ptr<ExpressionNode> condition_;
     std::vector<std::unique_ptr<StatementNode>> thenBranch_;
     std::vector<std::unique_ptr<StatementNode>> elseBranch_;
   public:
     ConditionalStatementNode(
         std::unique_ptr<ExpressionNode> condition,
         std::vector<std::unique_ptr<StatementNode>> thenBranch,
         std::vector<std::unique_ptr<StatementNode>> elseBranch,
         const std::string & file_name,
         int line,
         size_t column
     ) : StatementNode(file_name, line, column),
         condition_(std::move(condition)),
         thenBranch_(std::move(thenBranch)),
         elseBranch_(std::move(elseBranch)) {}
     void interpret(class Interpreter & interpreter) const override {
         // Evaluate condition
         auto val = condition_->evaluate(interpreter);
         bool cond = false;
         if (val.getType() == Symbols::Variables::Type::BOOLEAN) {
             cond = val.get<bool>();
         } else {
             throw std::runtime_error("Condition did not evaluate to boolean at " + filename_ +
                                      ":" + std::to_string(line_) + "," + std::to_string(column_));
         }
         // Execute appropriate branch
         const auto & branch = cond ? thenBranch_ : elseBranch_;
         for (const auto & stmt : branch) {
             stmt->interpret(interpreter);
         }
     }
     std::string toString() const override {
         return "ConditionalStatementNode at " + filename_ + ":" + std::to_string(line_);
     }
 };
 } // namespace Interpreter
 #endif // INTERPRETER_CONDITIONAL_STATEMENT_NODE_HPP
src/Interpreter/Interpreter.hpp
@@ -65,6 +65,12 @@
                    op.statement->interpret(*this);
                }
                break;
            case Operations::Type::Conditional:
                // if-else conditional block
                if (op.statement) {
                    op.statement->interpret(*this);
                }
                break;
            case Operations::Type::Return:
            case Operations::Type::Loop:
            case Operations::Type::Break:
@@ -72,7 +78,6 @@
            case Operations::Type::Block:
            case Operations::Type::Import:
            case Operations::Type::Error:
            case Operations::Type::Conditional:
                // TODO: implement these operations later
                break;
            default:
src/Modules/TypeofModule.hpp
New file
@@ -0,0 +1,45 @@
// TypeofModule.hpp
#ifndef MODULES_TYPEOFMODULE_HPP
#define MODULES_TYPEOFMODULE_HPP
#include <string>
#include <vector>
#include "BaseModule.hpp"
#include "ModuleManager.hpp"
#include "Symbols/Value.hpp"
#include "Symbols/VariableTypes.hpp"
namespace Modules {
/**
 * @brief Module providing a typeof() function.
 * Usage:
 *   typeof($var)            -> returns string name of type ("int", "string", etc.)
 *   typeof($var, "int")   -> returns bool indicating if type matches
 */
class TypeofModule : public BaseModule {
  public:
    void registerModule() override {
        auto &mgr = ModuleManager::instance();
        mgr.registerFunction("typeof", [](const std::vector<Symbols::Value> &args) {
            using namespace Symbols;
            if (args.size() == 1) {
                auto t = args[0].getType();
                return Value(Variables::TypeToString(t));
            } else if (args.size() == 2) {
                auto t = args[0].getType();
                std::string name = Variables::TypeToString(t);
                if (args[1].getType() != Variables::Type::STRING) {
                    throw std::runtime_error("Second argument to typeof must be string");
                }
                bool match = (name == args[1].get<std::string>());
                return Value(match);
            }
            throw std::runtime_error("typeof expects 1 or 2 arguments");
        });
    }
};
} // namespace Modules
#endif // MODULES_TYPEOFMODULE_HPP
src/Parser/Parser.cpp
@@ -4,6 +4,13 @@
#include "Interpreter/OperationsFactory.hpp"
#include "Lexer/Operators.hpp"
// Statements and expression building for conditional and block parsing
#include "Interpreter/ConditionalStatementNode.hpp"
#include "Interpreter/CallStatementNode.hpp"
#include "Interpreter/DeclareVariableStatementNode.hpp"
#include "Interpreter/ReturnStatementNode.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Interpreter/ExpressionBuilder.hpp"
// Additional necessary includes, if needed
namespace Parser {
@@ -58,6 +65,96 @@
    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
}
// Parse an if-else conditional statement
void Parser::parseIfStatement() {
    // 'if'
    auto ifToken = expect(Lexer::Tokens::Type::KEYWORD, "if");
    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
    auto condExpr = parseParsedExpression(Symbols::Variables::Type::BOOLEAN);
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, "{");
    // then branch
    std::vector<std::unique_ptr<Interpreter::StatementNode>> thenBranch;
    while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
        thenBranch.push_back(parseStatementNode());
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    // else branch
    std::vector<std::unique_ptr<Interpreter::StatementNode>> elseBranch;
    if (match(Lexer::Tokens::Type::KEYWORD, "else")) {
        expect(Lexer::Tokens::Type::PUNCTUATION, "{");
        while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
            elseBranch.push_back(parseStatementNode());
        }
        expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    }
    // build condition node
    auto condNode = buildExpressionFromParsed(condExpr);
    auto stmt = std::make_unique<Interpreter::ConditionalStatementNode>(
        std::move(condNode), std::move(thenBranch), std::move(elseBranch),
        this->current_filename_, ifToken.line_number, ifToken.column_number);
    // add conditional operation
    Operations::Container::instance()->add(
        Symbols::SymbolContainer::instance()->currentScopeName(),
        Operations::Operation{Operations::Type::Conditional, "", std::move(stmt)});
}
// Parse a single statement and return its StatementNode (for use in blocks)
std::unique_ptr<Interpreter::StatementNode> Parser::parseStatementNode() {
    // Return statement
    if (currentToken().type == Lexer::Tokens::Type::KEYWORD_RETURN) {
        auto tok = expect(Lexer::Tokens::Type::KEYWORD_RETURN);
        ParsedExpressionPtr expr = nullptr;
        if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ";")) {
            expr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
        }
        expect(Lexer::Tokens::Type::PUNCTUATION, ";");
        auto exprNode = expr ? buildExpressionFromParsed(expr) : nullptr;
        return std::make_unique<Interpreter::ReturnStatementNode>(
            std::move(exprNode), this->current_filename_, tok.line_number, tok.column_number);
    }
    // Function call statement
    if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
        peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
        auto idTok = expect(Lexer::Tokens::Type::IDENTIFIER);
        std::string funcName = idTok.value;
        expect(Lexer::Tokens::Type::PUNCTUATION, "(");
        std::vector<ParsedExpressionPtr> args;
        if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ")")) {
            while (true) {
                args.push_back(parseParsedExpression(Symbols::Variables::Type::NULL_TYPE));
                if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) continue;
                break;
            }
        }
        expect(Lexer::Tokens::Type::PUNCTUATION, ")");
        expect(Lexer::Tokens::Type::PUNCTUATION, ";");
        std::vector<std::unique_ptr<Interpreter::ExpressionNode>> exprs;
        exprs.reserve(args.size());
        for (auto &p : args) {
            exprs.push_back(buildExpressionFromParsed(p));
        }
        return std::make_unique<Interpreter::CallStatementNode>(
            funcName, std::move(exprs), this->current_filename_, idTok.line_number, idTok.column_number);
    }
    // Variable declaration
    if (Parser::variable_types.find(currentToken().type) != Parser::variable_types.end()) {
        auto type = parseType();
        auto idTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
        std::string name = idTok.value;
        if (!name.empty() && name[0] == '$') name = name.substr(1);
        expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "=");
        auto valExpr = parseParsedExpression(type);
        expect(Lexer::Tokens::Type::PUNCTUATION, ";");
        auto exprNode = buildExpressionFromParsed(valExpr);
        return std::make_unique<Interpreter::DeclareVariableStatementNode>(
            name, Symbols::SymbolContainer::instance()->currentScopeName(), type,
            std::move(exprNode), this->current_filename_, idTok.line_number, idTok.column_number);
    }
    reportError("Unexpected token in block");
    return nullptr;
}
void Parser::parseFunctionDefinition() {
    expect(Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION);
    Lexer::Tokens::Token     id_token         = expect(Lexer::Tokens::Type::IDENTIFIER);
src/Parser/Parser.hpp
@@ -6,6 +6,7 @@
#include <vector>
#include "BaseException.hpp"
#include "Interpreter/StatementNode.hpp"
#include "Lexer/Token.hpp"
#include "Lexer/TokenType.hpp"
#include "Parser/ParsedExpression.hpp"
@@ -169,6 +170,11 @@
    // parseStatement (updated to handle return)
    void parseStatement() {
        const auto & token_type = currentToken().type;
        // if-else conditional
        if (token_type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
            parseIfStatement();
            return;
        }
        if (token_type == Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION) {
            parseFunctionDefinition();
@@ -201,6 +207,10 @@
    void parseCallStatement();
    // Parse a return statement (e.g., return; or return expr;)
    void parseReturnStatement();
    // Parse an if-else conditional statement
    void parseIfStatement();
    // Parse a statement node for use inside blocks (not added to operation container)
    std::unique_ptr<Interpreter::StatementNode> parseStatementNode();
    // --- Parsing helper functions ---
src/VoidScript.hpp
@@ -9,6 +9,7 @@
#include "Modules/ModuleManager.hpp"
#include "Modules/PrintNlModule.hpp"
#include "Modules/PrintModule.hpp"
#include "Modules/TypeofModule.hpp"
#include "Parser/Parser.hpp"
class VoidScript {
@@ -54,6 +55,8 @@
        // Register built-in modules (print, etc.)
        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintModule>());
        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintNlModule>());
        // typeof() builtin
        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::TypeofModule>());
        this->files.emplace(this->files.begin(), file);
        lexer->setKeyWords(Parser::Parser::keywords);
test_scripts/arguments.vs
New file
@@ -0,0 +1,4 @@
if ($argc > 1) {
    printnl("Hello,", $argv[1]);
}
test_scripts/if_statements.vs
New file
@@ -0,0 +1,21 @@
string $test = "Test string";
if ($test == "Test string") {
    printnl("Test passed");
} else {
    printnl("Test failed");
}
bool $this_is_okay = true;
bool $this_is_not_okay = false;
if ($this_is_okay && $this_is_not_okay == false && $test == "Test string") {
    printnl("This is okay");
}
if ($this_is_okay == false) {
    printnl("This is not okay");
}
test_scripts/object.vs
New file
@@ -0,0 +1,4 @@
object $person = {
    name: "Szoni",
    age: 37
};
test_scripts/typeof.vs
New file
@@ -0,0 +1,7 @@
string $var1 = "Hello";
printnl(typeof($var1));
printnl("This is string: ", typeof($var1,"string"));
printnl("This is string: ", typeof($var1,"bool"));
printnl("This is a \"something\": ", typeof($var1,"something"));
tmp.vs
New file
@@ -0,0 +1,4 @@
int $x = 5;
printnl(typeof($x));
printnl(typeof($x, "int"));
printnl(typeof($x, "string"));