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