‌ For example, as you finish this homework, the expression in the figure 1. which (2 [*]y) + x consists of two binary operators (addition and multiplication) and two variables (x and y),

2.2 Simple Example

For example, as you finish this homework, the expression in the figure 1. which (2 [*]y) + x consists of two binary operators (addition and multiplication) and two variables (x and y), should be parsed implicitly during compilation (i.e. without writing an extra parsing code) due to (mainly) overloaded operator-*- and operator* methods in the abstract class.

Sym::Var x ■ “x”, y – “y”;

Sym : : Expr f-2.0*y + x;

This third-party syntactic-sugar will be explained in the following section 3. But roughly speaking, overloading operator-*- and operator* in the leaf class Sym::Expr let’s the compiler to apply the following replacement

Sym :: Expr f ■ Sym ::Expr::operator+ (Sym::Expr::operator *(2, y) ,x) ;

You will see in the implementation details that operator* and operator-*- are defined as a friend method of the class Sym::Expr. In the parameter passing, the first argu­ment is replaced by a Sym::Const(2) and set to the left hand side of the binary operator Sym::MulOp. Additionally. Sym::Var x(“x”) is set to the right hand side. Lastly, The ad­dition operator Sym::MulOp::operator+ is called by the returning instance. The method

3 Implementation

This homework includes, private and protected attributes, pure and virtual methods, overloaded methods, abstract classes, and inherited classes extensively. In brief, the figure 2 shows class hierarchy by inheritance. As a naming convention, the double underscore prefix indicates an abstract class like an interface in Java.

The root abstract class sym::__expr_t has the following four pure methods evaluation, differentiation, visualization, and comparison as in the figure 3. Their intended behavior is attached in a comment. We expect you to implement them in the leaf classes.

There are also three abstract children classes derived from the root class, wiiich are nullary operator (3.1), unary operator (3.2) and binary operator (3.3). These classes have some extra polymorhic methods like is_nullary to check of type of an instance in run-time.

Eventually this tree in figure 2 has seven leaf classes called Constant (3.1.1), Variable (3.1.2), Negation (3.2.1), Exponentiation (3.2.2), Addition (3.3.1), Multiplication (3.3.2), and Expression. Roughly speaking, for example, Addition (sym::AddOp) overrides such four pure functions; to act like addition, i.e. the additive group of real numbers.

sets the instance to the left hand side of a Sym::AddOp instance. Finally, Sym::Var y(“y”) is set to the right hand side and the addition instance is returned. The result is set to a Sym::Expr instance f. auto f_y ■ f.diff(y);

auto sanity « (f_y “” 2 ? “pass” : “fail”); std::cout << “check: ” << sanity << std::endl; std : :cout << f_y.eval() << std : :endl;

check: pass 2

3.1 sym::__nullary_op_t

The sym::___ nullary _op_t is an abstract class directly derived from sym::________ expr_t that

represents nullary operators, i.e. operators that take no arguments. It’s a baseline to inherit sym::Const (3.1.1) and sym::Var (3.1.2). Note also for those derived classes, you need to implement a getter as well as an explicit casting.

3.1.1 sym::Const

(5 Points): Here in this class, you will implement the following methods,

• sym::Const::Const: (constructor) takes a double as an argument and sets it to its attribute (let’s say) variable.

• sym::Const::get_value: (getter) returns the variable.

• sym::Const::operator double: (cast operator) returns the variable.

• sym::Const::eval: (overridden method) returns a pointer to a new sym::Const in­stance with the same variable.

• sym::Const::difF: (overridden method) returns a pointer to a new expression tree which is the derivative of this expression tree with respect to its input v.

• sym::Const::operator<<: (overridden method) writes variable to its input “output stream’’ and returns it.

• sym::Const::operator==: (overridden method) compares if its input (say other) is a sym::Const instance with the same variable as it’s variable.

3.1.2 sym::Var

(5 points) Here in this class, you will implement the following methods,

• sym::Var::Var: (constructor) takes a std::string as an argument and sets it to its attribute (let’s say) variable.

• sym::Var::get_variable: (getter) returns the variable.

• sym::Var::operator std::string: (cast operator) returns the pointer to the variable.

• sym::Var::eval: (overridden method) returns a pointer to a new sym::Var with the same variable unless it’s variable is in the input “variable map”, otherwise it returns a constant with a value which is mapped by the variable in the input “variable map”.

• sym::Var::diff: (overridden method) returns a pointer to a new expression tree which is the derivative of this expression tree with respect to its input v, i.e. it’s a zero- constant2 if variable equals v, one-constant’! otherwise.

• sym::Var::operator<<: (overridden method) writes variable to its input “output stream” and returns it. •

The sym::__unary_op_t is an abstract class directly derived from sym::__expr_t that represents unary operators, i.e. operators that take one argument. It’s a baseline to inherit sym::NegOp (3.2.1) and sym::ExpOp (3.2.2).

3.2.1 sym::NegOp

(15 points) Here in this class, you will implement the following methods,

• sym::NegOp::NegOp: (constructor) takes a expression sub-treepointed to by a root class pointer (i.e. sym::__expr_t) as an argument and sets it to its attribute (let’s say) operand.

• sym::NegOp::eval: (overridden method) returns a pointer to a new sym::Const with a negative “variable” if its’ operand is a constant with “variable”. Otherwise, it returns a pointer to a new negation instance whose operand points to a sub-tree that is equal to one pointed to by operand.

• sym::NegOp::difF: (overridden method) returns a pointer to a new expression tree that is equal to the differentiation of the tree pointed to by operand.

V(NegOp(op)) = NegOp(V(pp))

• sym::NegOp::operator<<: (overridden method) writes “-operand” to its input “output stream” and returns it in top-to-down fashion. Additionally, if it’s operand is not nullary, encloses it with parenthesis i.e. “-(operand)’’, please check the advice 2.

• sym::NegOp::operator==: (overridden method) compares if its input argument (say other) is a sym::NegOp instance with a sub-tree that is (recursively) equal to the one pointed to by operand.

3.2.2 sym::ExpOp

(15 points) Here in this class, you will implement the following methods,

• sym::ExpOp::ExpOp: (constructor) takes a expression sub-tree pointed to by a root class pointer (i.e. sym::__expr_t) as an argument and sets it to its attribute (let’s say) operand.

• sym::ExpOp::eval: (overridden method) returns a pointer to a new sym::Const with an exponent “variable” if its’ operand is a constant with “variable”. Otherwise, it returns a pointer to a new exponentiation instance whose operand points to a sub­tree that is equal to one pointed to by operand.

• sym::ExpOp::diff: (overridden method) returns a pointer to a new expression tree that is equal to the differentiation of the tree pointed to by operand.

V(ExpOp(op)) = Mul()p(V(op). ExpOp(op))

• sym::ExpOp::operator<<: (overridden method) writes “e” operand” to its input “output stream” and returns it in top-to-down fashion. Additionally, if it’s operand is not nullary. encloses it with parenthesis i.e. “e*(operand)’’, please check the advice

2. [2]

The sym::__binary_op_t is an abstract class directly derived from sym::__expr_t that represents binary operators, i.e. operators that take two arguments. It’s a baseline to inherit sym::AddOp (3.3.1) and sym::MulOp (3.3.2).

3.3.1 sym::AddOp

(25 points) Here in this class, you will implement the following methods,

• sym::AddOp::AddOp: (constructor) takes two expression sub-trees pointed to by a

root class pointers (i.e. sym::___ expr_t) as arguments and sets them to its attributes

(let’s say) right hand side and left hand side.

• sym::AddOp::eval: (overridden method) returns a pointer to a new expression tree (recursively) equal to the one hand side if the other side is a zero-constant. However, it returns a pointer to a constant with a variable summation of “valuel” and “value2” if both attributes are pointing to a valuel-constant and a value2-constant. Otherwise, it returns a pointer to a new addition instance whose attributes are pointing to two sub-trees which are equal to ones pointed by left hand side and right hand side respectively.

• sym::AddOp::diff: (overridden method) returns a pointer to a new expression tree that is equal to the differentiation of the tree pointed to by left hand side + right

hand side.

V(Add()p(//is, rhs)) = AddOp(V(f/is), V(rh.s))

• sym::AddOp::operator<<: (overridden method) writes “left hand side + right hand side” to its input “output stream” and returns it in top-to-down fashion. Addi­tionally, if any side is not nullary. encloses it with parenthesis e.g. “(left hand side)

4 right hand side” if only left side is non nullary, please check the advice 2.

• sym::AddOp”operator==: (overridden method) compares if its input argument (say other) is a sym::AddOp instance with two sub-trees that are (recursively) equal to ones pointed to by either sides reciprocally. For details please check the advice 3.

3.3.2 sym::MulOp

(25 points) Here in this class, you will implement the following methods,

• sym::MulOp::MulOp: (constructor) takes two expression sub-trees pointed to by a

root class pointers (i.e. sym::_____ expr_t) as arguments and sets them to its attributes

(let’s say) right hand side and left hand side.

• sym::MulOp::eval: (overridden method) returns a pointer to a new expression tree (recursively) equal to the one hand side if the other side is a one-constant. However, it returns a pointer to a constant with a variable product of “valuel” and “value2” if

both attributes are pointing to a ■valuel-constant and a value2-constant. Otherwise, it returns a pointer to a new multiplication instance whose attributes are pointing to two sub-trees which are equal to ones pointed by left hand side and right hand side

respectively.

• sym::MulOp::diff: (overridden method) returns a pointer to a new expression tree that is equal to the differentiation of the tree pointed to by left hand side times right hand side.

V(Mul()p(//i.s. rhs)) = MulOp(AddOp(V(//t.s), rhs).Add()p(//is. V(r/is)))

• sym::MulOp::operator<<: (overridden method) writes “left hand side [3]right hand side” to its input “output stream” and returns it in top-to-down fashion. Ad­ditionally, if any side is not nullary, encloses it with parenthesis e.g. “left hand side * (right hand side)” if only right side is non nullary, please check the advice 2.

3.4 sym::Expr

(7 points): The sym::Expr is an actual class directly derived from sym::_______________ expr.t

that represents expressions, i.e. a wrapper to contain pointers to exprssion tree instances. Implement following methods,

• sym::Expr::Expr: (constructor) takes an expression tree pointed to by a root class pointers (i.e. sym::__expr_t) as an argument and sets them to its attribute (let’s say) root.

• sym::MulOp::eval: (overridden method) returns a pointer to a new expression tree (recursively) equal to one pointed to by root.

• sym::MulOp::difF: (overridden method) returns a pointer to a new expression tree (recursively) equal to the derivative of one pointed to by root.

• sym::MulOp::operator<<: (overridden method) writes “root” to the input “output stream” and returns it.

• sym::MulOp::operator==: (overridden method) compares if its input argument (say other) is equal to the expression-tree pointed to by root.

3.5 overload.epp

(8 points): This is a separate file containing overloading of the following operators and methods

• operator-(const sym::_expr.t Acop)

• operator+(const sym::_oxpr J; &lhs, const sym::_jexpr_t Acrhs)

• operator+(const double lhs, const sym::_jexpr_t Acrhs)

• operator+(const sym::_expr_t Adhs, const double rhs)

• operator* (const sym::„expr_t Adhs, const sym::__expr_t Ac rhs)

• operator*(const double lhs, const sym::„(‘.xpr_t Acrhs)

• operator*(const sym::„expr_t Aclhs, const double rhs)

• exp(const sym::_expr_t Acop)

[*] sym::Var::operator==: (overridden method) compares if its input (say other) is a sym::Var instance with the same variable as it’s variable.

[2] sym::ExpOp::operator==: (overridden method) compares if its input argument (say other) is a sym::ExpOp instance with a sub-tree that is (recursively) equal to the one pointed to by operand.

[3]     sym::MulOp::operator==: (overridden method) compares if its input argument (say other) is a sym::MulOp instance with two sub-trees that are (recursively) equal to ones pointed to by either sides reciprocally. For details please check the advice 3.