From 68693bb7b71bb5721bdda9dc16948b750ea71065 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 19:00:42 +0000
Subject: [PATCH] fix object accessing

---
 src/Interpreter/MemberExpressionNode.hpp         |   40 ++++++++++
 src/Parser/Parser.cpp                            |   44 +++++++++-
 src/Interpreter/ExpressionBuilder.hpp            |   16 ++++
 tmp2.vs                                          |    1 
 src/Lexer/Lexer.cpp                              |    3 
 tmp_test2.vs                                     |    1 
 tmp_test3.vs                                     |    4 +
 tmp_test4.vs                                     |    4 +
 tmp_test.vs                                      |    6 +
 tmp_invalid.vs                                   |    1 
 src/Lexer/Operators.cpp                          |    2 
 src/Lexer/Operators.hpp                          |   38 ++++++---
 test_scripts/object.vs                           |   32 +++-----
 tmp_valid.vs                                     |    4 +
 temp_test.vs                                     |    1 
 src/Interpreter/DeclareVariableStatementNode.hpp |   14 +++
 16 files changed, 170 insertions(+), 41 deletions(-)

diff --git a/src/Interpreter/DeclareVariableStatementNode.hpp b/src/Interpreter/DeclareVariableStatementNode.hpp
index 875c87b..7dbfa3f 100644
--- a/src/Interpreter/DeclareVariableStatementNode.hpp
+++ b/src/Interpreter/DeclareVariableStatementNode.hpp
@@ -31,13 +31,25 @@
         ns(ns) {}
 
     void interpret(Interpreter & interpreter) const override {
+        // Evaluate the expression and enforce declared type matches actual value type
         Symbols::Value value = expression_->evaluate(interpreter);
+        // Check for duplicate declaration
         if (Symbols::SymbolContainer::instance()->exists(variableName_)) {
             throw std::runtime_error("Variable already declared: " + variableName_ + " File: " + filename_ +
                                      ", Line: " + std::to_string(line_) + ", Column: " + std::to_string(column_));
         }
+        // Enforce type correctness: the evaluated value must match the declared type
+        if (value.getType() != variableType_) {
+            using namespace Symbols::Variables;
+            std::string expected = TypeToString(variableType_);
+            std::string actual   = TypeToString(value.getType());
+            throw std::runtime_error("Type mismatch for variable '" + variableName_ +
+                                     "': expected '" + expected + "' but got '" + actual +
+                                     "' File: " + filename_ + ", Line: " + std::to_string(line_) +
+                                     ", Column: " + std::to_string(column_));
+        }
+        // Create and add the variable symbol
         const auto variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
-
         Symbols::SymbolContainer::instance()->add(variable);
     }
 
diff --git a/src/Interpreter/ExpressionBuilder.hpp b/src/Interpreter/ExpressionBuilder.hpp
index f5f4323..778dbf3 100644
--- a/src/Interpreter/ExpressionBuilder.hpp
+++ b/src/Interpreter/ExpressionBuilder.hpp
@@ -10,6 +10,8 @@
 #include "Interpreter/LiteralExpressionNode.hpp"
 #include "Interpreter/UnaryExpressionNode.hpp"  // <-- új include
 #include "Interpreter/CallExpressionNode.hpp"
+#include "Interpreter/MemberExpressionNode.hpp"
+#include "Interpreter/ObjectExpressionNode.hpp"
 #include "Interpreter/ObjectExpressionNode.hpp"
 #include "Parser/ParsedExpression.hpp"
 
@@ -27,6 +29,20 @@
 
         case Kind::Binary:
             {
+                if (expr->op == "->") {
+                    auto objExpr = buildExpressionFromParsed(expr->lhs);
+                    std::string propName;
+                    // RHS parsed expression should be a literal string or variable parser node
+                    if (expr->rhs->kind == ParsedExpression::Kind::Literal &&
+                        expr->rhs->value.getType() == Symbols::Variables::Type::STRING) {
+                        propName = expr->rhs->value.get<std::string>();
+                    } else if (expr->rhs->kind == ParsedExpression::Kind::Variable) {
+                        propName = expr->rhs->name;
+                    } else {
+                        throw std::runtime_error("Invalid property name in member access");
+                    }
+                    return std::make_unique<Interpreter::MemberExpressionNode>(std::move(objExpr), propName);
+                }
                 auto lhs = buildExpressionFromParsed(expr->lhs);
                 auto rhs = buildExpressionFromParsed(expr->rhs);
                 return std::make_unique<Interpreter::BinaryExpressionNode>(std::move(lhs), expr->op, std::move(rhs));
diff --git a/src/Interpreter/MemberExpressionNode.hpp b/src/Interpreter/MemberExpressionNode.hpp
new file mode 100644
index 0000000..83ba51a
--- /dev/null
+++ b/src/Interpreter/MemberExpressionNode.hpp
@@ -0,0 +1,40 @@
+#ifndef INTERPRETER_MEMBER_EXPRESSION_NODE_HPP
+#define INTERPRETER_MEMBER_EXPRESSION_NODE_HPP
+
+#include "ExpressionNode.hpp"
+#include "Symbols/Value.hpp"
+#include <stdexcept>
+
+namespace Interpreter {
+
+// Expression node for member access: object->property
+class MemberExpressionNode : public ExpressionNode {
+  public:
+    MemberExpressionNode(std::unique_ptr<ExpressionNode> objectExpr, std::string propertyName)
+        : objectExpr_(std::move(objectExpr)), propertyName_(std::move(propertyName)) {}
+
+    Symbols::Value evaluate(Interpreter & interpreter) const override {
+        Symbols::Value objVal = objectExpr_->evaluate(interpreter);
+        if (objVal.getType() != Symbols::Variables::Type::OBJECT) {
+            throw std::runtime_error("Attempted to access member '" + propertyName_ + "' of non-object");
+        }
+        const auto &map = std::get<Symbols::Value::ObjectMap>(objVal.get());
+        auto it = map.find(propertyName_);
+        if (it == map.end()) {
+            throw std::runtime_error("Property '" + propertyName_ + "' not found on object");
+        }
+        return it->second;
+    }
+
+    std::string toString() const override {
+        return objectExpr_->toString() + "->" + propertyName_;
+    }
+
+  private:
+    std::unique_ptr<ExpressionNode> objectExpr_;
+    std::string                     propertyName_;
+};
+
+} // namespace Interpreter
+
+#endif // INTERPRETER_MEMBER_EXPRESSION_NODE_HPP
\ No newline at end of file
diff --git a/src/Lexer/Lexer.cpp b/src/Lexer/Lexer.cpp
index d229c6b..7f5d36a 100644
--- a/src/Lexer/Lexer.cpp
+++ b/src/Lexer/Lexer.cpp
@@ -272,7 +272,8 @@
             { &OPERATOR_RELATIONAL, Tokens::Type::OPERATOR_RELATIONAL },
             { &OPERATOR_INCREMENT,  Tokens::Type::OPERATOR_INCREMENT  },
             { &OPERATOR_ASSIGNMENT, Tokens::Type::OPERATOR_ASSIGNMENT },
-            { &OPERATOR_LOGICAL,    Tokens::Type::OPERATOR_LOGICAL    }
+            { &OPERATOR_LOGICAL,    Tokens::Type::OPERATOR_LOGICAL    },
+            { &PUNCTUATION,         Tokens::Type::PUNCTUATION         }
         };
 
         for (const auto & [vec_ptr, type] : two_char_op_types) {
diff --git a/src/Lexer/Operators.cpp b/src/Lexer/Operators.cpp
index 0a34433..bf2cadc 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/Operators.hpp b/src/Lexer/Operators.hpp
index 794ef86..b134c45 100644
--- a/src/Lexer/Operators.hpp
+++ b/src/Lexer/Operators.hpp
@@ -24,6 +24,9 @@
 bool isBinaryOperator(const std::string & op);
 
 inline int getPrecedence(const std::string & op) {
+    if (op == "->") {
+        return 5;  // Member access has highest precedence
+    }
     if (op == "u-" || op == "u+" || op == "u!") {
         return 4;
     }
@@ -59,25 +62,36 @@
                                       std::vector<Parser::ParsedExpressionPtr> & output_queue) {
     // Literal operands: number, string, or keyword literals (e.g., true/false/null)
     if (token.type == Tokens::Type::NUMBER) {
-        // Numeric literal: auto-detect integer/double/float
-        output_queue.push_back(
-            Parser::ParsedExpression::makeLiteral(
-                Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
+        // Numeric literal: only allowed if expected is numeric or unspecified
+        if (expected_var_type != Symbols::Variables::Type::NULL_TYPE &&
+            expected_var_type != Symbols::Variables::Type::INTEGER &&
+            expected_var_type != Symbols::Variables::Type::DOUBLE &&
+            expected_var_type != Symbols::Variables::Type::FLOAT) {
+            return false;
+        }
+        // Auto-detect or cast to expected numeric type
+        auto val = Symbols::Value::fromString(token.value, /*autoDetectType*/ true);
+        output_queue.push_back(Parser::ParsedExpression::makeLiteral(val));
         return true;
     }
     if (token.type == Tokens::Type::STRING_LITERAL) {
-        // String literal: use literal value
-        output_queue.push_back(
-            Parser::ParsedExpression::makeLiteral(
-                Symbols::Value(token.value)));
+        // String literal: only allowed if expected is string or unspecified
+        if (expected_var_type != Symbols::Variables::Type::NULL_TYPE &&
+            expected_var_type != Symbols::Variables::Type::STRING) {
+            return false;
+        }
+        output_queue.push_back(Parser::ParsedExpression::makeLiteral(Symbols::Value(token.value)));
         return true;
     }
     if (token.type == Tokens::Type::KEYWORD) {
         // Keyword literal: e.g., true, false, null
-        // Auto-detect boolean or null as needed
-        output_queue.push_back(
-            Parser::ParsedExpression::makeLiteral(
-                Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
+        auto val = Symbols::Value::fromString(token.value, /*autoDetectType*/ true);
+        auto vtype = val.getType();
+        // only allowed if expected matches or unspecified
+        if (expected_var_type != Symbols::Variables::Type::NULL_TYPE && expected_var_type != vtype) {
+            return false;
+        }
+        output_queue.push_back(Parser::ParsedExpression::makeLiteral(val));
         return true;
     }
     if (token.type == Tokens::Type::VARIABLE_IDENTIFIER) {
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index 1739a20..6aa0348 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -406,7 +406,32 @@
             continue;
         }
 
-        if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == "(") {
+        // Member access: '->'
+        else if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == "->") {
+            std::string op(token.lexeme);
+            // Shunting-yard: handle operator precedence
+            while (!operator_stack.empty()) {
+                const std::string & top = operator_stack.top();
+                if ((Lexer::isLeftAssociative(op) && Lexer::getPrecedence(op) <= Lexer::getPrecedence(top)) ||
+                    (!Lexer::isLeftAssociative(op) && Lexer::getPrecedence(op) < Lexer::getPrecedence(top))) {
+                    operator_stack.pop();
+                    // Binary operator: pop two operands
+                    if (output_queue.size() < 2) {
+                        Parser::reportError("Malformed expression", token);
+                    }
+                    auto rhs = std::move(output_queue.back()); output_queue.pop_back();
+                    auto lhs = std::move(output_queue.back()); output_queue.pop_back();
+                    output_queue.push_back(Lexer::applyOperator(op, std::move(rhs), std::move(lhs)));
+                } else {
+                    break;
+                }
+            }
+            operator_stack.push(op);
+            consumeToken();
+            expect_unary = true;
+        }
+        // Grouping parentheses
+        else if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == "(") {
             operator_stack.push("(");
             consumeToken();
             expect_unary = true;
@@ -520,11 +545,18 @@
             operator_stack.push(op);
             consumeToken();
             expect_unary = true;
-        } else if (token.type == Lexer::Tokens::Type::NUMBER || token.type == Lexer::Tokens::Type::STRING_LITERAL ||
-                   token.type == Lexer::Tokens::Type::KEYWORD ||
-                   token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
-            if (Lexer::pushOperand(token, expected_var_type, output_queue) == false) {
-                Parser::reportError("Invalid type", token, "literal or variable");
+        } else if (token.type == Lexer::Tokens::Type::NUMBER
+                   || token.type == Lexer::Tokens::Type::STRING_LITERAL
+                   || token.type == Lexer::Tokens::Type::KEYWORD
+                   || token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER
+                   || token.type == Lexer::Tokens::Type::IDENTIFIER) {
+            if (token.type == Lexer::Tokens::Type::IDENTIFIER) {
+                // Treat bare identifiers as variable references for member access
+                output_queue.push_back(ParsedExpression::makeVariable(token.value));
+            } else {
+                if (Lexer::pushOperand(token, expected_var_type, output_queue) == false) {
+                    Parser::reportError("Invalid type", token, "literal or variable");
+                }
             }
             consumeToken();
             expect_unary = false;
diff --git a/temp_test.vs b/temp_test.vs
new file mode 100644
index 0000000..fb45629
--- /dev/null
+++ b/temp_test.vs
@@ -0,0 +1 @@
+object $person = { name: "Test" }; printnl($person->name);
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
index 9b9ce5e..63526fb 100644
--- a/test_scripts/object.vs
+++ b/test_scripts/object.vs
@@ -1,36 +1,28 @@
 
 object $person = {
-    name: "Szoni",
-    double age: 37.6
+    string name: "Szoni",
+    int age: 37
 };
+
+printnl("Hello, ", $person->name, " your age is: ", $person->age);
 
 
 object $person2 = {
     string name: "Not Szoni",
     int age: 37,
-    object $children: {
+    object children: {
         string name: "Child1",
         int age: 10
     }
 };
+printnl("Person2: ", $person2->name, " age: ", $person2->age, " child: ", $person2->children->name, " age: ", $person2->children->age);
 
-object $children2 = {
-     string name: "Child2",
-        int age: 15
-};
+object $test = $person2;
+printnl("Person2: ", $test->name, " age: ", $test->age, " child: ", $test->children->name, " age: ", $test->children->age);
 
-object $family = {
-           $children: $children, // this is valid too
-    object $children: $children2
-};
-
-object $family3 = {
-    string age: 10  // this is invalid, drops error
-};
+string $person_name = $person->name;
+printnl("Person name: ", $person_name);
 
 
-
-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
+printnl("Child1 old age: ",$person2->children->age);
+$person2->children->age = $person2->children->age + 2;
diff --git a/tmp2.vs b/tmp2.vs
new file mode 100644
index 0000000..d64dd76
--- /dev/null
+++ b/tmp2.vs
@@ -0,0 +1 @@
+object $o = 123;
diff --git a/tmp_invalid.vs b/tmp_invalid.vs
new file mode 100644
index 0000000..b9ade37
--- /dev/null
+++ b/tmp_invalid.vs
@@ -0,0 +1 @@
+object $o = {int age: "abc"};
diff --git a/tmp_test.vs b/tmp_test.vs
new file mode 100644
index 0000000..c113a19
--- /dev/null
+++ b/tmp_test.vs
@@ -0,0 +1,6 @@
+object $person = {
+    name: "Szoni",
+    age: 37
+};
+
+printnl("Hello, ", $person->name);
diff --git a/tmp_test2.vs b/tmp_test2.vs
new file mode 100644
index 0000000..9a23b13
--- /dev/null
+++ b/tmp_test2.vs
@@ -0,0 +1 @@
+object $child = { name: "Test", age: 5 }; object $parent = { child: $child }; printnl("Name: ",$parent->child->name, " Age: ", $parent->child->age);
diff --git a/tmp_test3.vs b/tmp_test3.vs
new file mode 100644
index 0000000..86943cf
--- /dev/null
+++ b/tmp_test3.vs
@@ -0,0 +1,4 @@
+object $child1 = { name: "Child1", age: 10 };
+object $child2 = { name: "Child2", age: 15 };
+object $family = { $children: $child1, object $children: $child2 };
+printnl($family->children->name);
diff --git a/tmp_test4.vs b/tmp_test4.vs
new file mode 100644
index 0000000..94515f9
--- /dev/null
+++ b/tmp_test4.vs
@@ -0,0 +1,4 @@
+object $person = { name: "Test", age: 20 };
+printnl(typeof($person));
+printnl(typeof($person->name));
+printnl(typeof($person->age));
diff --git a/tmp_valid.vs b/tmp_valid.vs
new file mode 100644
index 0000000..9ccf014
--- /dev/null
+++ b/tmp_valid.vs
@@ -0,0 +1,4 @@
+object $o = { int age: 11 };
+printnl("Age: ",$o->age);
+printnl("Type: ", typeof($o));
+printnl("Type: ", typeof($o->age));
\ No newline at end of file

--
Gitblit v1.9.3