$24
A Generic Interpreter
The interpereters number-lambda.rkt and string-lambda.rkt are extremely similar. The former is our usual interpreter with first-class functions. The latter has string expressions and values instead of number expressions and values, and it was created by copying all of the code in number-lambda.rktand modifying it to work with strings.
Instead of copying the parse, interp, and lookup functions, we’d like to have a single implementation that works with both. We don’t want numbers to work in string programs or strings to work in number programs, though; we want to keep the languages separate. When we start with an S-expression, we will decide whether to parse and interpret it as a number program or a string program.
The interpereters number-lit-lambda.rkt and string-lit-lambda.rkt are even more similar than number-lambda.rkt and string-lambda.rkt, because the literal-expression constructor is renamed from numE or strE to litE in both interpreters. Similarly, numV and strV are renamed to litV. New entry points parse/num and interp/num or parse/str and interp/str will let us pick which kind of program we want to parse and interpret.
Your task is to merge number-lit-lambda.rkt and string-lit-lambda.rkt into a single module with a single implementation of parse, interp, and lookup. All of the tests in number-lit-lambda.rkt and string-lit-lambda.rkt should work unmodified with the merged implementation.
To merge the implementation, you will need to change Exp to be parameterized over a type for literals, so that parse/num returns a (Exp Number), and parse/str returns a (Exp String). You’ll have to parameterize other types, and you’ll have to parameterize functions over other functions.