From 664ba87732f9feffcbcbe10c2f41c5288f90a533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludwig=20Mostr=C3=B6m?= <ludmo578@student.liu.se> Date: Thu, 27 Mar 2025 12:11:48 +0100 Subject: [PATCH] UnaryOp implemented ("!" and "-") with tests --- include/ast/NodeValue.h | 4 ++ include/ast/UnaryOpNode.h | 85 +++++++++++++++++++++++++++++++ source/ast/NodeValue.cc | 14 ++++++ source/ast/UnaryOpNode.cc | 77 ++++++++++++++++++++++++++++ tests/TestUnaryOpNode.cc | 103 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 283 insertions(+) create mode 100644 include/ast/UnaryOpNode.h create mode 100644 source/ast/UnaryOpNode.cc create mode 100644 tests/TestUnaryOpNode.cc diff --git a/include/ast/NodeValue.h b/include/ast/NodeValue.h index 93c2f19..2904a20 100644 --- a/include/ast/NodeValue.h +++ b/include/ast/NodeValue.h @@ -130,4 +130,8 @@ NodeValue operator||(const NodeValue& lhs, const NodeValue& rhs); // Power operation NodeValue pow(const NodeValue& lhs, const NodeValue& rhs); +// Unary operators +NodeValue operator-(const NodeValue& val); +NodeValue operator!(const NodeValue& val); + } // namespace funk diff --git a/include/ast/UnaryOpNode.h b/include/ast/UnaryOpNode.h new file mode 100644 index 0000000..3255594 --- /dev/null +++ b/include/ast/UnaryOpNode.h @@ -0,0 +1,85 @@ +/** + * @file BinaryOpNode.h + * @brief Defines the UnaryOpNode class for representing unary operations in the Funk AST. + */ +#pragma once + +#include "ast/ExpressionNode.h" +#include "ast/LiteralNode.h" + +namespace funk +{ + +/** + * @brief Enumeration of all supported unary operators in the Funk language. + */ +enum class UnaryOp +{ + NEGATE, ///< Negation operator (-) + NOT, ///< Logical NOT operator (!) +}; + +/** + * @brief Converts a unary operator to a string representation. + * @param op The unary operator + * @return String representation of the operator + */ +String op_to_s(UnaryOp op); + +/** + * @brief Class representing a unary operation in the Funk AST. + * + * UnaryOpNode represents operations that require one operand and + * an operator. These include arithmetic negation and logical NOT. + */ +class UnaryOpNode : public ExpressionNode +{ +public: + /** + * @brief Constructs a unary operation node with an expression and an operator. + * @param op The unary operator to apply + * @param expr The expression to apply the operator to + */ + UnaryOpNode(UnaryOp op, ExpressionNode* expr); + + /** + * @brief Virtual destructor for proper cleanup of resources. + */ + ~UnaryOpNode() override; + + /** + * @brief Evaluates the unary operation. + * @return A node representing the result of applying the operator to the operand + */ + Node* evaluate() const override; + + /** + * @brief Converts the unary operation to a string representation. + * @return String representation of the unary operation + */ + String to_s() const override; + + /** + * @brief Gets the value that this unary operation evaluates to. + * @return The evaluated value of this unary operation + */ + NodeValue get_value() const override; + + /** + * @brief Gets the unary operator used in this operation. + * @return The unary operator + */ + UnaryOp get_op() const; + + /** + * @brief Gets the expression that the operator is applied to. + * @return Pointer to the expression + */ + ExpressionNode* get_expr() const; + +private: + UnaryOp op; ///< The unary operator + ExpressionNode* expr; ///< The expression to apply the operator to +}; + +} // namespace funk \ No newline at end of file diff --git a/source/ast/NodeValue.cc b/source/ast/NodeValue.cc index ecec453..8206672 100644 --- a/source/ast/NodeValue.cc +++ b/source/ast/NodeValue.cc @@ -216,6 +216,20 @@ NodeValue pow(const NodeValue& lhs, const NodeValue& rhs) else { return std::pow(lhs.cast<double>(), rhs.cast<double>()); } } +NodeValue operator-(const NodeValue& val) +{ + if (!val.is_numeric()) { throw TypeError("Cannot negate non-numeric value"); } + + if (val.is_a<int>()) { return -val.get<int>(); } + return -val.cast<double>(); +} + +NodeValue operator!(const NodeValue& val) +{ + if (!val.is_a<bool>()) { throw TypeError("Cannot apply logical NOT to non-boolean value"); } + return !val.get<bool>(); +} + template bool NodeValue::is_a<int>() const; template bool NodeValue::is_a<double>() const; template bool NodeValue::is_a<bool>() const; diff --git a/source/ast/UnaryOpNode.cc b/source/ast/UnaryOpNode.cc new file mode 100644 index 0000000..830c959 --- /dev/null +++ b/source/ast/UnaryOpNode.cc @@ -0,0 +1,77 @@ +#include "ast/UnaryOpNode.h" + +namespace funk +{ + +UnaryOpNode::UnaryOpNode(UnaryOp op, ExpressionNode* expr) : + ExpressionNode(expr->get_location()), op(op), expr(expr) { + + }; + +UnaryOpNode::~UnaryOpNode() +{ + delete expr; +} + +Node* UnaryOpNode::evaluate() const +{ + if (cached_eval) { return cached_eval; } + + NodeValue expr_value{expr->get_value()}; + NodeValue result{}; + + try + { + switch (op) + { + case UnaryOp::NEGATE: result = -expr_value; break; + case UnaryOp::NOT: result = !expr_value; break; + default: throw RuntimeError(location, "Invalid unary operator"); + } + } + catch (const TypeError& e) + { + throw TypeError(location, e.what()); + } + catch (const RuntimeError& e) + { + throw RuntimeError(location, e.what()); + } + + return cached_eval = new LiteralNode(location, result); +} + +String UnaryOpNode::to_s() const +{ + return "(" + op_to_s(op) + expr->to_s() + ")"; +} + +NodeValue UnaryOpNode::get_value() const +{ + ExpressionNode* result{dynamic_cast<ExpressionNode*>(evaluate())}; + if (!result) { throw RuntimeError(location, "Unary operation did not evaluate to an expression"); } + + return result->get_value(); +} + +UnaryOp UnaryOpNode::get_op() const +{ + return op; +} + +ExpressionNode* UnaryOpNode::get_expr() const +{ + return expr; +} + +String op_to_s(UnaryOp op) +{ + switch (op) + { + case UnaryOp::NEGATE: return "-"; + case UnaryOp::NOT: return "!"; + default: return "UNKNOWN"; + } +} + +}; // namespace funk \ No newline at end of file diff --git a/tests/TestUnaryOpNode.cc b/tests/TestUnaryOpNode.cc new file mode 100644 index 0000000..2be026b --- /dev/null +++ b/tests/TestUnaryOpNode.cc @@ -0,0 +1,103 @@ +#include "ast/LiteralNode.h" +#include "ast/UnaryOpNode.h" +#include "utils/Common.h" +#include <gtest/gtest.h> + +using namespace funk; + +class TestUnaryOpNode : public ::testing::Test +{ +protected: + void SetUp() override + { + // Setup common test objects + loc = SourceLocation{"test.funk", 0, 0}; + } + + void TearDown() override + { + // Cleanup code if needed + } + + SourceLocation loc{"test.funk", 0, 0}; +}; + +TEST_F(TestUnaryOpNode, Construction) +{ + LiteralNode* intVal = new LiteralNode(loc, 5); + LiteralNode* boolVal = new LiteralNode(loc, false); + + UnaryOpNode negateNode(UnaryOp::NEGATE, intVal); + UnaryOpNode notNode(UnaryOp::NOT, boolVal); + + ASSERT_EQ(negateNode.get_op(), UnaryOp::NEGATE); + ASSERT_EQ(negateNode.get_expr(), intVal); + + ASSERT_EQ(notNode.get_op(), UnaryOp::NOT); + ASSERT_EQ(notNode.get_expr(), boolVal); +} + +TEST_F(TestUnaryOpNode, NegateEvaluation) +{ + LiteralNode* val1 = new LiteralNode(loc, 5); + LiteralNode* val2 = new LiteralNode(loc, 3.14); + + UnaryOpNode negateInt(UnaryOp::NEGATE, val1); + UnaryOpNode negateDouble(UnaryOp::NEGATE, val2); + + ASSERT_EQ(negateInt.get_value().get<int>(), -5); + ASSERT_DOUBLE_EQ(negateDouble.get_value().get<double>(), -3.14); +} + +TEST_F(TestUnaryOpNode, NotEvaluation) +{ + LiteralNode* val1 = new LiteralNode(loc, true); + LiteralNode* val2 = new LiteralNode(loc, false); + + UnaryOpNode notTrue(UnaryOp::NOT, val1); + UnaryOpNode notFalse(UnaryOp::NOT, val2); + + ASSERT_FALSE(notTrue.get_value().get<bool>()); + ASSERT_TRUE(notFalse.get_value().get<bool>()); +} + +TEST_F(TestUnaryOpNode, ErrorCases) +{ + // Negate string value + { + LiteralNode* val = new LiteralNode(loc, String("test")); + UnaryOpNode node(UnaryOp::NEGATE, val); + + ASSERT_THROW(node.get_value(), TypeError); + } + + // Negate boolean value, apparently this works in c++ but it's ugly + { + LiteralNode* val = new LiteralNode(loc, true); + UnaryOpNode node(UnaryOp::NEGATE, val); + + ASSERT_THROW(node.get_value(), TypeError); + } + + // Logical NOT on non-boolean value + { + LiteralNode* val = new LiteralNode(loc, 5); + UnaryOpNode node(UnaryOp::NOT, val); + + ASSERT_THROW(node.get_value(), TypeError); + } +} + +TEST_F(TestUnaryOpNode, StringRepresentation) +{ + LiteralNode* val = new LiteralNode(loc, 5); + UnaryOpNode node(UnaryOp::NEGATE, val); + + ASSERT_EQ(node.to_s(), "(-5)"); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- GitLab