If you're still struggling to get a good language design, look back at the notes from the "Introduction to Languages" lecture: they discuss how a language consists of data, operations on the data, and control commands for ordering operations. What are each of these for a tax form program? It might be easiest to think in terms of the commands first, and the data/control second:
The commands are things like "request user input", "compute value", "conditional branch (to schedule)", "request multiple inputs" and "print taxes owed" (these are analogous to commands like "display" in the slideshow language and "ask", "branch on results" and "display status" in the tutoring system of Lab 5). The language design you turned in should have supported at least all five of these commands, or something close to them.
For each of these commands, what data do they operate on? "Request user input" needs to know what prompt to give the user. "Compute value" needs to know what computation to perform, and on which lines. And so on. Each of these gives rise to a structure for a different kind of command.
What's the control? A sequence of commands. Do we run them in
order, or do we ever branch (like in timecond
)? Think about the
schedules -- how should you handle those?
Once you have answers to these questions, a data definition emerges. You need one for the commands (corresponding to the lines in the form). Whether you need one for data depends on whether all of the command data consists of simple built-in types (like strings, symbols, and numbers), or whether you need something more complex (like a slide). A tax program should capture how you order the commands.
If you still don't have a workable language design that follows the above format, don't start on the implementation until you revise your design. You'll waste your time and not get any closer to a working implementation.
Recall that when we introduced the slideshow program, we talked about all the functionality we might want, then started with a smaller portion and built it up over time (as when you added overlays). Take the same approach here. Pick a couple of (easier) commands, and get the interpreter running on those commands. Test it, then expand it to handle more commands.
Why do it this way? Because (a) it makes the project more manageable, and (b) it hopefully gives you something partially working to turn in, rather than a larger jumble of code that doesn't work. The large jumble demonstrates less of what you understand than a working prototype on a smaller language.
Here's another construct that can help on the implementation:
apply : (arg1 ... argn -> alpha) list-of-length-n ->
alpha
apply
takes a function (of any number of arguments)
and a list (with the number of arguments that the function needs) and
calls the function with the arguments in the list as arguments. Here
are some examples:
(apply + (list 1 2 3))
returns 6
(apply (lambda (x y) (+ (* x x) (* y y))) (list 3 4))
returns 25
(apply (lambda (x y) (+ (* x x) (* y y))) (list 3 4
5))
returns an error, since the list has more elements than the
function expects.
apply
is useful if you need to query the user for a
list of arguments and want to pass them to a function, or if you
gather a list of arguments from some data structures and want to pass
them to a function.
Leave yourself enough time to do this assignment! This project is certainly doable with what we've covered this term, but it does require you to pull together all the material we've covered so far. For most of you, that will take a bit more time than you're probably expecting.