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:
= in _let should have one space on either side._let expression should have parentheses only when needed to avoid ambiguity when following the rule that _let is “weaker” than either + or *. Specifically, a + or * continues a _let body (in the same sense that * continues a + argument) unless parentheses prevent it. Also, parentheses should be put around the smallest expression needed to resolve an ambiguity. These goals turn out to have the following implications: _let expression will not need any more parentheses than if that right-hand side or body were the whole expression._let needs parentheses when it is nested immediately as the left argument of a + or * expression._let needs parentheses when it is nested immediately as the right argument of an unparenthesized * where _let would have needed parentheses in the surrounding context (that is, if the _let used in place of the whole * would need parentheses, then it still needs parentheses within the right-hand size of *)._in part of a _let expression should be on a new line, and _in should be preceded with enough spaces to make it line up under _let. Also, an extra space should appear after _in so that it takes the same space as _let. To add enough space before _let, your helper for pretty_print will need a new accumulator argument. Note that std::ostream has a tellp method that reports how many characters have been written to the stream — at least in the case of a string stream, which you can assume from now on. You may find it useful to keep track of where a recent newline was printed, because the printer for _let can then infer the current column when starts printing (and then it can make sure that _in starts on the same column).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