From fb8d8f9f5bb4a1f7736d927a346d4bf834a28ffa Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 17:25:48 +0000
Subject: [PATCH] add if else statements
---
src/Parser/Parser.cpp | 97 +++++++++++++++++++
src/VoidScript.hpp | 3
tmp.vs | 4
test_scripts/if_statements.vs | 21 ++++
src/Interpreter/BinaryExpressionNode.hpp | 7 +
src/Parser/Parser.hpp | 10 ++
src/Interpreter/ConditionalStatementNode.hpp | 57 +++++++++++
test_scripts/object.vs | 4
src/Modules/TypeofModule.hpp | 45 +++++++++
src/Interpreter/Interpreter.hpp | 7 +
test_scripts/arguments.vs | 4
test_scripts/typeof.vs | 7 +
12 files changed, 264 insertions(+), 2 deletions(-)
diff --git a/src/Interpreter/BinaryExpressionNode.hpp b/src/Interpreter/BinaryExpressionNode.hpp
index 3281aab..fdf8d9c 100644
--- a/src/Interpreter/BinaryExpressionNode.hpp
+++ b/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_);
}
diff --git a/src/Interpreter/ConditionalStatementNode.hpp b/src/Interpreter/ConditionalStatementNode.hpp
new file mode 100644
index 0000000..254f4a9
--- /dev/null
+++ b/src/Interpreter/ConditionalStatementNode.hpp
@@ -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
\ No newline at end of file
diff --git a/src/Interpreter/Interpreter.hpp b/src/Interpreter/Interpreter.hpp
index fe18030..328d6af 100644
--- a/src/Interpreter/Interpreter.hpp
+++ b/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:
diff --git a/src/Modules/TypeofModule.hpp b/src/Modules/TypeofModule.hpp
new file mode 100644
index 0000000..f38cd54
--- /dev/null
+++ b/src/Modules/TypeofModule.hpp
@@ -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
\ No newline at end of file
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index 7e99d37..e1e9776 100644
--- a/src/Parser/Parser.cpp
+++ b/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);
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index 0eb1180..d2c826c 100644
--- a/src/Parser/Parser.hpp
+++ b/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 ---
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 6ed0e02..8bbf24d 100644
--- a/src/VoidScript.hpp
+++ b/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);
diff --git a/test_scripts/arguments.vs b/test_scripts/arguments.vs
new file mode 100644
index 0000000..235482d
--- /dev/null
+++ b/test_scripts/arguments.vs
@@ -0,0 +1,4 @@
+if ($argc > 1) {
+ printnl("Hello,", $argv[1]);
+
+}
\ No newline at end of file
diff --git a/test_scripts/if_statements.vs b/test_scripts/if_statements.vs
new file mode 100644
index 0000000..42ddec5
--- /dev/null
+++ b/test_scripts/if_statements.vs
@@ -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");
+}
\ No newline at end of file
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
new file mode 100644
index 0000000..fa6d218
--- /dev/null
+++ b/test_scripts/object.vs
@@ -0,0 +1,4 @@
+object $person = {
+ name: "Szoni",
+ age: 37
+};
\ No newline at end of file
diff --git a/test_scripts/typeof.vs b/test_scripts/typeof.vs
new file mode 100644
index 0000000..4eccd9c
--- /dev/null
+++ b/test_scripts/typeof.vs
@@ -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"));
\ No newline at end of file
diff --git a/tmp.vs b/tmp.vs
new file mode 100644
index 0000000..b29f906
--- /dev/null
+++ b/tmp.vs
@@ -0,0 +1,4 @@
+int $x = 5;
+printnl(typeof($x));
+printnl(typeof($x, "int"));
+printnl(typeof($x, "string"));
--
Gitblit v1.9.3