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