From 6cc2945c1d1e6ca7bad0542c79de423df5e2db8b Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 18:13:29 +0000
Subject: [PATCH] implements object type
---
src/Parser/Parser.cpp | 48 ++++++++++++++++
src/Symbols/VariableTypes.hpp | 6 +
src/Interpreter/ExpressionBuilder.hpp | 10 +++
src/Parser/ParsedExpression.hpp | 12 +++
src/Interpreter/ObjectExpressionNode.hpp | 35 +++++++++++
src/Lexer/Operators.cpp | 2
src/Parser/Parser.hpp | 2
test_scripts/object.vs | 36 +++++++++++
src/Lexer/TokenType.hpp | 3 +
src/Symbols/Value.hpp | 10 +++
10 files changed, 157 insertions(+), 7 deletions(-)
diff --git a/src/Interpreter/ExpressionBuilder.hpp b/src/Interpreter/ExpressionBuilder.hpp
index 602f603..f5f4323 100644
--- a/src/Interpreter/ExpressionBuilder.hpp
+++ b/src/Interpreter/ExpressionBuilder.hpp
@@ -10,6 +10,7 @@
#include "Interpreter/LiteralExpressionNode.hpp"
#include "Interpreter/UnaryExpressionNode.hpp" // <-- új include
#include "Interpreter/CallExpressionNode.hpp"
+#include "Interpreter/ObjectExpressionNode.hpp"
#include "Parser/ParsedExpression.hpp"
namespace Parser {
@@ -46,6 +47,15 @@
}
return std::make_unique<Interpreter::CallExpressionNode>(expr->name, std::move(callArgs));
}
+ case Kind::Object:
+ {
+ std::vector<std::pair<std::string, std::unique_ptr<Interpreter::ExpressionNode>>> members;
+ members.reserve(expr->objectMembers.size());
+ for (const auto &p : expr->objectMembers) {
+ members.emplace_back(p.first, buildExpressionFromParsed(p.second));
+ }
+ return std::make_unique<Interpreter::ObjectExpressionNode>(std::move(members));
+ }
}
throw std::runtime_error("Unknown ParsedExpression kind");
diff --git a/src/Interpreter/ObjectExpressionNode.hpp b/src/Interpreter/ObjectExpressionNode.hpp
new file mode 100644
index 0000000..885b4d0
--- /dev/null
+++ b/src/Interpreter/ObjectExpressionNode.hpp
@@ -0,0 +1,35 @@
+#ifndef INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
+#define INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
+
+#include "ExpressionNode.hpp"
+#include "Symbols/Value.hpp"
+#include <vector>
+#include <string>
+#include <memory>
+
+namespace Interpreter {
+
+class ObjectExpressionNode : public ExpressionNode {
+ public:
+ using ObjectMap = Symbols::Value::ObjectMap;
+
+ explicit ObjectExpressionNode(std::vector<std::pair<std::string, std::unique_ptr<ExpressionNode>>> members)
+ : members_(std::move(members)) {}
+
+ Symbols::Value evaluate(Interpreter & interpreter) const override {
+ ObjectMap obj;
+ for (const auto &kv : members_) {
+ obj[kv.first] = kv.second->evaluate(interpreter);
+ }
+ return Symbols::Value(obj);
+ }
+
+ std::string toString() const override { return "[object]"; }
+
+ private:
+ std::vector<std::pair<std::string, std::unique_ptr<ExpressionNode>>> members_;
+};
+
+} // namespace Interpreter
+
+#endif // INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
\ No newline at end of file
diff --git a/src/Lexer/Operators.cpp b/src/Lexer/Operators.cpp
index 5fd9f81..0a34433 100644
--- a/src/Lexer/Operators.cpp
+++ b/src/Lexer/Operators.cpp
@@ -8,7 +8,7 @@
const std::vector<std::string> OPERATOR_LOGICAL = { "&&", "||" };
const std::vector<std::string> OPERATOR_ARITHMETIC = { "+", "-", "*", "/", "%", "!" };
-const std::vector<std::string> PUNCTUATION = { "(", ")", "{", "}", "[", "]", ",", ";" };
+const std::vector<std::string> PUNCTUATION = { "(", ")", "{", "}", "[", "]", ",", ";", ":" };
bool contains(const std::vector<std::string> & vec, const std::string & value) {
return std::find(vec.begin(), vec.end(), value) != vec.end();
diff --git a/src/Lexer/TokenType.hpp b/src/Lexer/TokenType.hpp
index e29ba04..386964b 100644
--- a/src/Lexer/TokenType.hpp
+++ b/src/Lexer/TokenType.hpp
@@ -25,6 +25,7 @@
KEYWORD_DOUBLE,
KEYWORD_FLOAT,
KEYWORD_BOOLEAN,
+ KEYWORD_OBJECT,
KEYWORD_FUNCTION_DECLARATION,
KEYWORD_RETURN,
KEYWORD_NULL,
@@ -69,6 +70,8 @@
return "KEYWORD_FLOAT";
case Lexer::Tokens::Type::KEYWORD_BOOLEAN:
return "KEYWORD_BOOLEAN";
+ case Lexer::Tokens::Type::KEYWORD_OBJECT:
+ return "KEYWORD_OBJECT";
case Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION:
return "KEYWORD_FUNCTION";
case Lexer::Tokens::Type::KEYWORD_RETURN:
diff --git a/src/Parser/ParsedExpression.hpp b/src/Parser/ParsedExpression.hpp
index 06a462b..4acff85 100644
--- a/src/Parser/ParsedExpression.hpp
+++ b/src/Parser/ParsedExpression.hpp
@@ -16,7 +16,7 @@
using ParsedExpressionPtr = std::unique_ptr<ParsedExpression>;
struct ParsedExpression {
- enum class Kind : std::uint8_t { Literal, Variable, Binary, Unary, Call };
+ enum class Kind : std::uint8_t { Literal, Variable, Binary, Unary, Call, Object };
Kind kind;
@@ -29,6 +29,7 @@
ParsedExpressionPtr rhs;
// For function call arguments
std::vector<ParsedExpressionPtr> args;
+ std::vector<std::pair<std::string, ParsedExpressionPtr>> objectMembers;
// Constructor for literal
static ParsedExpressionPtr makeLiteral(const Symbols::Value & val) {
@@ -70,6 +71,13 @@
expr->kind = Kind::Call;
expr->name = name;
expr->args = std::move(arguments);
+ return expr;
+ }
+ // Constructor for object literal
+ static ParsedExpressionPtr makeObject(std::vector<std::pair<std::string, ParsedExpressionPtr>> members) {
+ auto expr = std::make_unique<ParsedExpression>();
+ expr->kind = Kind::Object;
+ expr->objectMembers = std::move(members);
return expr;
}
@@ -116,6 +124,8 @@
auto funcSym = std::static_pointer_cast<Symbols::FunctionSymbol>(symbol);
return funcSym->returnType();
}
+ case Kind::Object:
+ return Symbols::Variables::Type::OBJECT;
default:
throw std::runtime_error("Unknown expression kind");
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index e1e9776..1739a20 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -6,6 +6,7 @@
#include "Lexer/Operators.hpp"
// Statements and expression building for conditional and block parsing
#include "Interpreter/ConditionalStatementNode.hpp"
+// #include "Interpreter/ForStatementNode.hpp" // removed until for-in loops are implemented
#include "Interpreter/CallStatementNode.hpp"
#include "Interpreter/DeclareVariableStatementNode.hpp"
#include "Interpreter/ReturnStatementNode.hpp"
@@ -34,6 +35,7 @@
{ "string", Lexer::Tokens::Type::KEYWORD_STRING },
{ "boolean", Lexer::Tokens::Type::KEYWORD_BOOLEAN },
{ "bool", Lexer::Tokens::Type::KEYWORD_BOOLEAN },
+ { "object", Lexer::Tokens::Type::KEYWORD_OBJECT },
// ... other keywords ...
};
@@ -44,6 +46,7 @@
{ Lexer::Tokens::Type::KEYWORD_STRING, Symbols::Variables::Type::STRING },
{ Lexer::Tokens::Type::KEYWORD_NULL, Symbols::Variables::Type::NULL_TYPE },
{ Lexer::Tokens::Type::KEYWORD_BOOLEAN, Symbols::Variables::Type::BOOLEAN },
+ { Lexer::Tokens::Type::KEYWORD_OBJECT, Symbols::Variables::Type::OBJECT },
};
void Parser::parseVariableDefinition() {
@@ -357,6 +360,51 @@
while (true) {
auto token = currentToken();
+ // Object literal: { key: value, ... }
+ if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "{") {
+ // Consume '{'
+ consumeToken();
+ std::vector<std::pair<std::string, ParsedExpressionPtr>> members;
+ // Parse members until '}'
+ if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
+ while (true) {
+ // Optional type tag before key
+ Symbols::Variables::Type memberType = Symbols::Variables::Type::UNDEFINED_TYPE;
+ if (Parser::variable_types.find(currentToken().type) != Parser::variable_types.end()) {
+ memberType = parseType();
+ }
+ // Key must be an identifier or variable identifier
+ if (currentToken().type != Lexer::Tokens::Type::IDENTIFIER &&
+ currentToken().type != Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
+ reportError("Expected identifier for object key");
+ }
+ std::string key = currentToken().value;
+ // Strip '$' if present
+ if (!key.empty() && key[0] == '$') {
+ key = key.substr(1);
+ }
+ consumeToken();
+ // Expect ':' delimiter
+ expect(Lexer::Tokens::Type::PUNCTUATION, ":");
+ // Parse value expression (pass tag type if provided)
+ Symbols::Variables::Type expectType = (memberType == Symbols::Variables::Type::UNDEFINED_TYPE)
+ ? Symbols::Variables::Type::NULL_TYPE
+ : memberType;
+ auto valueExpr = parseParsedExpression(expectType);
+ members.emplace_back(key, std::move(valueExpr));
+ if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+ continue;
+ }
+ break;
+ }
+ }
+ // Expect closing '}'
+ expect(Lexer::Tokens::Type::PUNCTUATION, "}");
+ // Create object literal parsed expression
+ output_queue.push_back(ParsedExpression::makeObject(std::move(members)));
+ expect_unary = false;
+ continue;
+ }
if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == "(") {
operator_stack.push("(");
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index d2c826c..56839fa 100644
--- a/src/Parser/Parser.hpp
+++ b/src/Parser/Parser.hpp
@@ -209,6 +209,8 @@
void parseReturnStatement();
// Parse an if-else conditional statement
void parseIfStatement();
+ // Parse a for-in loop over object members
+ void parseForStatement();
// Parse a statement node for use inside blocks (not added to operation container)
std::unique_ptr<Interpreter::StatementNode> parseStatementNode();
diff --git a/src/Symbols/Value.hpp b/src/Symbols/Value.hpp
index f7040c0..4985d5b 100644
--- a/src/Symbols/Value.hpp
+++ b/src/Symbols/Value.hpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <stdexcept>
#include <string>
+#include <map>
#include <variant>
#include "VariableTypes.hpp"
@@ -12,7 +13,8 @@
class Value {
public:
- using Variant = std::variant<int, double, float, std::string, bool>;
+ using ObjectMap = std::map<std::string, Value>;
+ using Variant = std::variant<int, double, float, std::string, bool, ObjectMap>;
Value() = default;
@@ -27,6 +29,10 @@
Value(const char * v) : value_(std::string(v)) { type_ = Symbols::Variables::Type::STRING; }
Value(bool v) : value_(v) { type_ = Symbols::Variables::Type::BOOLEAN; }
+ /**
+ * @brief Construct an object value from a map of member names to Values.
+ */
+ Value(const ObjectMap & v) : value_(v) { type_ = Symbols::Variables::Type::OBJECT; }
Value(const std::string & str, bool autoDetectType) { *this = fromString(str, autoDetectType); }
@@ -70,6 +76,8 @@
return val ? "true" : "false";
} else if constexpr (std::is_same_v<T, std::string>) {
return val;
+ } else if constexpr (std::is_same_v<T, ObjectMap>) {
+ return "[object]";
} else {
return std::to_string(val);
}
diff --git a/src/Symbols/VariableTypes.hpp b/src/Symbols/VariableTypes.hpp
index 99c4802..e389b57 100644
--- a/src/Symbols/VariableTypes.hpp
+++ b/src/Symbols/VariableTypes.hpp
@@ -7,7 +7,7 @@
namespace Symbols::Variables {
-enum class Type : std::uint8_t { INTEGER, DOUBLE, FLOAT, STRING, BOOLEAN, NULL_TYPE, UNDEFINED_TYPE };
+enum class Type : std::uint8_t { INTEGER, DOUBLE, FLOAT, STRING, BOOLEAN, OBJECT, NULL_TYPE, UNDEFINED_TYPE };
const std::unordered_map<std::string, Type> StringToTypeMap = {
{ "int", Type::INTEGER },
@@ -17,6 +17,7 @@
{ "bool", Type::BOOLEAN },
{ "boolean", Type::BOOLEAN },
{ "null", Type::NULL_TYPE },
+ { "object", Type::OBJECT },
{ "undefined", Type::UNDEFINED_TYPE },
};
@@ -26,8 +27,9 @@
{ Type::FLOAT, "float" },
{ Type::STRING, "string" },
{ Type::BOOLEAN, "bool" },
+ { Type::OBJECT, "object" },
{ Type::NULL_TYPE, "null" },
- { Type::UNDEFINED_TYPE, "undeffined" },
+ { Type::UNDEFINED_TYPE, "undefined" },
};
inline static std::string TypeToString(Symbols::Variables::Type type) {
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
index fa6d218..9b9ce5e 100644
--- a/test_scripts/object.vs
+++ b/test_scripts/object.vs
@@ -1,4 +1,36 @@
+
object $person = {
name: "Szoni",
- age: 37
-};
\ No newline at end of file
+ double age: 37.6
+};
+
+
+object $person2 = {
+ string name: "Not Szoni",
+ int age: 37,
+ object $children: {
+ string name: "Child1",
+ int age: 10
+ }
+};
+
+object $children2 = {
+ string name: "Child2",
+ int age: 15
+};
+
+object $family = {
+ $children: $children, // this is valid too
+ object $children: $children2
+};
+
+object $family3 = {
+ string age: 10 // this is invalid, drops error
+};
+
+
+
+printnl("Hello, ", $person->name); // prints out: Hello, Szoni
+println("Type: ", typeof($person); // prints out: Type: object
+println("Type: ", typeof($person2->name)); // prints out: Type: string
+println("Type: ", typeof($person2->age)); // prints out: Type: double
--
Gitblit v1.9.3