7 files modified
9 files added
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | #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" |
| | | |
| | |
| | | |
| | | 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)); |
| New file |
| | |
| | | #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 |
| | |
| | | { &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) { |
| | |
| | | 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(); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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) { |
| | |
| | | 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; |
| | |
| | | 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; |
| New file |
| | |
| | | object $person = { name: "Test" }; printnl($person->name); |
| | |
| | | |
| | | 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; |
| New file |
| | |
| | | object $o = 123; |
| New file |
| | |
| | | object $o = {int age: "abc"}; |
| New file |
| | |
| | | object $person = { |
| | | name: "Szoni", |
| | | age: 37 |
| | | }; |
| | | |
| | | printnl("Hello, ", $person->name); |
| New file |
| | |
| | | object $child = { name: "Test", age: 5 }; object $parent = { child: $child }; printnl("Name: ",$parent->child->name, " Age: ", $parent->child->age); |
| New file |
| | |
| | | object $child1 = { name: "Child1", age: 10 }; |
| | | object $child2 = { name: "Child2", age: 15 }; |
| | | object $family = { $children: $child1, object $children: $child2 }; |
| | | printnl($family->children->name); |
| New file |
| | |
| | | object $person = { name: "Test", age: 20 }; |
| | | printnl(typeof($person)); |
| | | printnl(typeof($person->name)); |
| | | printnl(typeof($person->age)); |
| New file |
| | |
| | | object $o = { int age: 11 }; |
| | | printnl("Age: ",$o->age); |
| | | printnl("Type: ", typeof($o)); |
| | | printnl("Type: ", typeof($o->age)); |