Add a variant for _let expressions to your set of Expr classes, and make sure it implements all of the existing methods of Expr. Naturally, you should add tests, and make sure you get back to full test coverage with your additions.

Although _let always binds a variable, a _let expression doesn't necessarily have any variable expressions. That is, the “left-hand side” of a binding is always a name and not an expression in general. So, has_variable for a _let expression should return true only if the right-hand side or body expression contains a variable.

For print, a _let expression should always print with parentheses, using ( before the _let and ) after the body. No spaces should be around =, and only a single space should appear after _let and before/after _in.

Here's an example of a correctly printed expression:

(_let x=5 _in ((_let y=3 _in (y+2))+x))

 

For pretty_print:

Here's an example of a correctly pretty-printed expression:

_let x = 5
_in (_let y = 3
_in y + 2) + x

If you have a helper method such as pretty_print_at to implement pretty_print so far, you are free to add arguments to the helper method. You should not use global variables.

Making pretty_print work correctly for _let is meant to be a little tedious/tricky. You will need good tests!

Hint on parentheses: While the rules for parentheses may sound complicated, you can reason about the cases locally if you “accumulate” two separate pieces of information passed to pretty_print_at: the precedence level for using parentheses and whether _let forms need parentheses. Trying to collapse that into one number by assigning a precedence to _let turns out not to work well.

Hint on newlines: For tracking the last place where a newline is written, consider passing a std::streampos& argument to pretty_print_at, in addition to any accumulators. The position of the most recent newline is a kind of state that needs to be adjusted while going “down” into recursive calls but also back “up” from recursive calls, and passing a std::streampos& lets you assign to the argument in method and make the change visible to a caller.

 

More examples

Correct, where the interp result would be 26:

5 * (_let x = 5
     _in  x) + 1

In that example, a _let is on the right-hand side of a *, and parentheses would be needed if the _let were by itself in in place of the multiplication (i.e., the following is also correct):

(_let x = 5
_in  x) + 1

The following also correct, but for a different expression than the first one, where the  interp result would be 30:

5 * _let x = 5
  _in  x + 1

The following example is incorrect as output for pretty_print, because it has unnecessary parentheses (for an expression whose interp result is 30):

5 * (_let x = 5
  _in  x + 1)

The one is incorrect, because the right-hand side of the * would not need parentheses by itself; it would print (correctly) as

_let x = 5
_in  x + 1

Here's another incorrect output for pretty_print output, where the expression means the same as the one above with the interp result 26, but it can be printed with parentheses moved around a smaller expression (as above):

(5 * _let x = 5
     _in  x) + 1