« A Beautiful Language | Main | Progress on AspectJ 5 »
January 15, 2005
Little languages
In my last post I wrote that I had been dipping into the book "Structure and Interpretation of Computer Programs", and I observed that I'd built far more abstractions and interfaces than I had descriptive languages (described on the back cover as the three major techniques for controlling the complexity of large software systems).
Probably because this put back into my mind the possibility of writing a little language to help with a task, I found the need to create one a couple of weeks ago. The problem was the test cases for a feature I added to AspectJ in the run-up to Christmas. The feature implements pointcut rewriting for maximum efficiency of matching. As a simple example, if you write a pointcut
pointcut foo() : execution(* Foo+.*(..)) && within(org.xyz.*);
Then AspectJ's weaver (in the AspectJ 5 codebase) will rewrite it to:
pointcut foo() : within(org.xyz.*) && execution(* Foo+.*(..));
It does this because matching of within pcds is faster than matching of execution pcds, so by testing the within first, we can fail fast if it returns false. The rewriting rules are quite interesting in themselves and maybe I'll blog about them one day too. For now, lets press on.
Obviously, I wrote a whole bunch of test cases that made sure that rewritten pointcuts had the correct form. The problem is that I was lazy in writing those orignal tests, and used Pointcut.toString() as the basis for pointcut comparisons in some of the test cases. This works well when the rewriting gives a defined order for the rewritten expression (as in the example above), but turns out not to work so reliably when the rewriting comes across two terms with equal precedence. For example, given a pointcut "this(Foo) && this(Goo) && this(Boo)" the three terms can legitimately come in any order and the rewriting would be considered correct.
On my laptop, and on my linux box, and on Andy's laptop, by a quirk of implementation, the rewriting algorithm always returns the pointcut expression rewritten in the same order and I could get away with a toString comparison. On the build machine (typical), once in a while, the order would come out different and a test would fail. After a few random failed builds we'd had enough of this and I set about to fix it.
The brute force way to rewrite those tests would be to start walking the Pointcut AST produced by the rewriting, checking at each node for the elements we expect, and being tolerant of ordering where it didn't matter. There are quite a lot of tests, and this approach would have involved writing a lot of tedious code. So what I did instead is to invent a little language for specifying what the rewritten pointcut should be. To make life easy it uses RPN (reverse polish notation) for specifying pointcut expressions. The test cases now just call assertEquals(Pointcut pointcut, String spec) in place of the previous toString comparison. For the first example I gave above, the specification would be:
and within(org.xyz.*) execution(* Foo+.*(..))
The little language supports one extra keyword, "anyorder". The spec for the "this" example could be written:
or anyorder this(Foo) or anyorder this(Boo) this(Goo)
To parse the little language and verify a pointcut is just two small java methods. To make it extra easy to tokenize, I use % as the token delimiter since spaces can legitimately appear in pcds. So the actual test spec for the above example would be:
or%anyorder%this(Foo)%or%anyorder%this(Boo)%this(Goo)
It doesn't have to be pretty... it's just a tool to get a job done - and a very effective one at that. It saved me a lot of time, code, and effort.
Posted by adrian at January 15, 2005 12:38 PM [permalink]
Comments
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)