So, last post I introduced you to a new filter API. After some iterations, we have arrived to a nice, flexible filter implementation 🙂 As we wanted filters to be rich objects, able to combine themselves to produce new filters, we designed a hierarchy:
- StackFilter defines methods #and: and #or: , which allow filter combination. Both messages return a BooleanFilter
- BooleanFilter is a filter subclass, which know how to actually combine two filters.
- SelectorFilter is a filter for an specific selector or collection of selectors.
- KernelClassesFilter filters out common classes message sends (like those from Boolean, the Collection Hierarchy, etc)
- BlockFilter provides a way to create filters from any (boolean) block.
Filters handle one context at a time. Their most important responsibility is to know whether a given context should be displayed or not, through #shouldDisplay: message. For instance:
#BlockFilter>>shouldDisplay: aContext ^self block value: aContext.
And then you can combine a couple of filters, like this:
fromBlockFilter := [:ctx | ctx isNotNil] asFilter. doItFilter:= SelectorFilter forSelector: #doIt. doItFilter and: fromBlockFilter.
Which in turn return a BooleanFilter that can be combined again, and so on. Then, the responsibility of filtering the stack relies in the debugger:
filterStack ^self class filterCommonMessageSends ifTrue:[self stack reject: [:aContext | (self enabledFilters anySatisfy:[:aFilter | aFilter shouldDisplay: aContext])and: [aContext = self currentContext ] ]
But then we realized that we needed not filtering the top context, because we usually want to know where we actually are; and also the first n contexts prior to the top one. So finally the code looks like this:
filterStack ^self class filterCommonMessageSends ifTrue:[ activeFilters := self enabledFilters. newStack := OrderedCollection new. activeFilters do: [ :aFilter | "first loop: add to the newStack those contexts that should not be displayed" self stack do: [ :aContext | [aFilter shouldDisplay: aContext ] whileFalse: [ newStack add: aContext. ctx := aContext ] ]. "second loop: add the top contexts to keep track of where in the execution we are" (self stack dropWhile: [:each | each ~= ctx ]) do: [ :context | (aFilter shouldDisplay: context) whileTrue: [ newStack add: context ]]] newStack
I think the most interesting filter is BooleanFilter, which represents the combination of two filters, by a boolean operator (namely #and: and #or: messages). At first I thought of having #AndFilter and #OrFilter, but the I realized that the only difference between them was the boolean operation; so I decided to generalize that behavior by using reflection:
#BooleanFilter>>shouldDisplay: aContext ^ (self filters first shouldDisplay: aContext) perform: booleanOperator with: [self filters last shouldDisplay: aContext]
That code could easily be extended to handle more than two filter combination, by folding #shouldDisplay: message send results.
And then, the next challenge was to expose the filter API to the user. We wanted to have default filters (kernel classes, do it, nil messages filters) as global configuration, but we wanted to let the users create new filters and publish them as global configuration if they wished to. So we decided to create a dictionary, whose keys would be the filter classes and booleans as values (meaning if the given filter is enabled or not). So, for global configuration, you’ll see something like this on the Settings Browser:
So finally, to avoid having lots of class variables in SpecDebugger to toggle filters, we decided to create a new widget, whose responsibility would be to handle filtering actions. But for the moment, that responsibility still remains on the debugger.
And that’s pretty much it.
Now, a few words, not so unrelated. This year’s Google Summer of Code is ending, and even tough i didn’t make it to the final goal (remember I told you we’d call Heimdall to open the Bifröst?) due to some technical restrictions, I want to say that I’ve learnt a lot, and that I am really thankful to my mentors and to all the Pharo community for the support they’ve given me through this months. I will keep on working on the debugger, but since it won’t be for gsoc anymore… thanks.
So, thank you for reading, and stay tuned!