I write this blog entry while I work on this story. If you need to refresh your memory about Story #3, check the analysis post.
I've decided to roughly time my process for this implementation. However, I have many distractions: several of my students are going through mock interviews today, and I have to be on hand to handle fires; other students email me with questions and requests (like getting access to a subversion repository); and I'm trying to rip three versions of The Wall to my computer today. And I'm writing this blog post as I go (although I'm not counting those minutes).
The Implementation
The Lexer. I started with the lexer. To fully parse ARGV[1], I figured I could do it all in the lexer or break it apart in the lexer and parse out integer in the parser. Doing everything in the lexer seems simpler since it's done in one place, but in thinking about it some more, I realized that it puts too much responsibility on the lexer, making it more complicated. It doesn't hurt that this will mostly be handled by the parser anyway in the future!
So I needed to recognize ARGV, [, and ]. (The compiler already recognizes integers.) With a fair number of interruptions and distractions, this took me 40 elapsed minutes. It was probably more like 10 or 15 minutes of real work.
The Parser. So here's a surprise. I did too much work for the lexer. I finished the parser in about 10 minutes, and I didn't need to use the new tokens from my lexer. This suffices in ANTLR:
expression : 'ARGV[' INTEGER ']' -> ^(ARGV INTEGER)
I don't need the ARGV for any reason after the parsing, so why bother to recognize it separately? I did however, create a temporary AST type named "ARGV" as seen in the rewrite rule.
So I took out the changes in my lexer, and I do the work in the parser. I know it will change later, but since I'm not sure how that will play out, I'll toss what I did now.
Compilation. According to the clock, this took me around 40 minutes or so, but part of that time was spent talking to a delusional Packers fan about football. (For the record, anyone not a Bears fan is, by definition, delusional.)
A couple things struck me while working on this. First, my compiler is working with the raw ASTs from ANTLR. They're not the nicest structures to work with since they're so generalized. More importantly, they don't lead to good OO code at all; it's all switch statements. The thing is, I'm not sure when I should switch over to my "tree intermediate representation" that I've cooked over the last few years. These TIR objects implement the visitor pattern, so the various compiler algorithms are nicely implemented in an OO way.
Second, command-line arguments in PIR (that's "Parrot Intermediate Representation") required a compiler directive:
.param pmc argv
The thing is, this should be necessary only once in a PIR subroutine. I'm not sure what the Parrot interpreter will do if I include it more than once. In an addition expression, ARGV[0] + ARGV[1], it matters! I could always include the directive, but that would mean changing all of my previous acceptance tests. I balk at that not because of the work involved (ten minutes, tops) but because it suggests I should be looking for a different solution. So I need to figure a way to add that directive zero or one times as necessary. This is going to require more thought.
And this brings me to the third thing that I've discovered: I'm implementing only half the story! I can use a command-line argument only in place of a single integer; I cannot use them in an addition expression! Ooops.
I had to go back to see how I had phrased Story #3, and it does, in fact, promise the use of a command-line argument in place of any literal integer.
There is this tension in writing a story between making it too trivial and too much. Story #3 is the right size in any practical situation. However, it does not mean I need to implement the whole story. So this is a good place for me to break and to close out this blog entry. I'll finish the rest of Story #3 and post a summary of the results.