Condensed Implementation Strategy

Now that all the supporting pieces are in place, we are in a position to finally approach the actual implementation of matches: as a circuit, the evaluation contexts of which reside in class based characters.

[…]

At this point, there are three different cases to consider: that the current pattern character is a star, that the current pattern character is a pound, and that the current pattern character is something other than a star or a pound. Of course, since we have a distinct class per case, we will let polymorphism deal with this. But how do we phrase the question that needs to be asked? We should ask ourselves first what is it that needs to be distinguished. > From a simplistic point of view, we are moving the execution of ifTrue:ifFalse: down to the implementation of PICs in the virtual machine.

Most interestingly, upon quick inspection, we will find that every potential direction in which to move is known at this point. Actually, there is a quite limited amount of them. So much so, in fact, that there is just one of them: the only thing that can happen is that patternIndex and matchIndex are decremented, because matches: is going from right to left. If this step can be taken, then matches: would continue matching backwards with different end points. If it cannot be taken, it is because the context is a star character and matching needs to continue in a left-to-right fashion from the beginning of the pattern and the string. Therefore, we should let the corresponding characters tell the string what to do according to their identity. […] > The constraints squeeze the answer, quite literally, from nothing. It is almost a miracle.

[…] And that is it. Yes, this rather tiny bit of behavior completes the implementation of the message startingAt:continueBackwardsMatches:from:. We can do this because the complexity of the problem is encoded in the information space being traversed. Note how the particular structure of the space lets our implementation decide quite simple things. Given a unique traversal direction, should it be taken? Not only that is a rather simple thing to determine — the decision is done by polymorphism so ifTrue:ifFalse: does not even enter the picture at all! There is nothing to say because there is nothing to do. This is a mark of proper, artful design.

We could even start suspecting that this implementation might be quite fast. But let’s not get ahead of ourselves. We still need to complete the implementation traversal of matches: first. Now we need to implement ifYouMatch:. . . for stars, pounds, and the other characters.

And here, it is at this exact point that the full benefit of classes comes to fruition. Because it is due to the existence of the classes we created, and to the fact that the contexts they imply are specific enough, that behavior can proceed without asking any unnecessary questions. It just runs like electrons flowing down a copper wire. They do not ask for permission. They do not get into ifTrue:ifFalse: existential considerations. They just behave that way because *they cannot help themselves*.

This is how we have designed our implementation. Now we will see our guiding principles in action. As before, let’s spell out the shape of the message we are trying to implement first, so we can refer to things by their names.

CondensedClassBasedCharacter>> ifYouMatch: aCharacter let: aPatternString startingAt: aNewPatternIndex continueBackwardsMatches: aMatchString from: aNewMatchIndex

This message will have three implementations: one for “ordinary” characters, implemented in the class shown above, one for pound characters implemented in the class of pound characters, and one for star characters implemented in the class of star characters.

So, if the receiver is a pound character, how should this be implemented? Clearly, no checking needs to be done because the pound character matches everything. In this case, the unique traversal step can always be taken. Therefore, we simply let the intervals shrink and pass the baton to the string again. In other words,

CondensedPoundCharacter>> ifYouMatch: aCharacter let: aPatternString startingAt: aNewPatternIndex continueBackwardsMatches: aMatchString from: aNewMatchIndex ^ aPatternString startingAt: aNewPatternIndex continueBackwardsMatches: aMatchString from: aNewMatchIndex

This implementation does pretty much nothing. And here, nothing is meant quite literally. In particular, note that *there is not even a single send of* ==! Also, pay attention to how the existence of a context representing the pound character makes it possible to let things happen in a *mandatory* fashion, without it being necessary to forcefully tell objects what to do nor to draw any distinctions whatsoever.

But what if the receiver was an “ordinary” character? In that case, the context may determine that the step should not be taken. This would occur only when the receiver and the character passed as an argument do not match. Therefore, a simple refinement to the implementation above would let the traversal carry on without difficulty.

CondensedClassBasedCharacter>> ifYouMatch: aCharacter let: aPatternString startingAt: aNewPatternIndex continueBackwardsMatches: aMatchString from: aNewMatchIndex ^(self matches: aCharacter) and: [ aPatternString startingAt: aNewPatternIndex continueBackwardsMatches: aMatchString from: aNewMatchIndex ]

Again, not a single occurrence of ifTrue:ifFalse:. Now, it could argued that eventually there will be sends of ==, happening somewhere in the implementation of matches: for characters. However, they will only occur because the value of the comparison cannot be determined at design time. In other words, those == sends will draw distinctions the value of which can only be known at run time. Finally, how should this message be implemented for star characters? Clearly,

[…]

This completes the implementation of backwards matches:.

Let’s take a quick break and go over our example to see how what we wrote so far would work.

’a#c*fg#ij*x#z’ matches: ’abcQQfgfghijQQxyz’ "true"

The pattern would receive matches:. As per the implementation of matches:, the pattern would begin matching backwards. The first step of this process is to ask the pattern’s $z to continue backwards matching if it matches the string’s $z. The character receiving this request is an ordinary character because it represents $z. Because of how ordinary characters are designed to fulfil the request from the pattern, the pattern’s $z checks whether it matches the string’s $z or not. Since it does, it tells the pattern to continue backwards matching within the constrained range provided by the pattern in the first place. Thus, our example has been reduced by one character, as shown below.

’a#c*fg#ij*x#’ matches: ’abcQQfgfghijQQxy’

[…]

[…] By means of polymorphism alone, the matching direction has been switched from backwards to forward. This can be done because, as all potential cases are modeled by classes, it is possible to just do what needs to be done for every particular situation without needing to do any thinking whatsoever — in other words, *it is possible to take action without having to rediscover what the context is, because that knowledge is implied by the very context in which action takes place to begin with.*

Now that matching backwards is taken care of, we need to implement how patterns will match forward. In order to continue painting the implementation of matches:, then, the first thing we need to do is to provide a method for the message below.

ClassBasedCharacterArray>> startingAt: patternIndex upTo: maxPatternIndex continueForwardMatches: aMatchString from: matchIndex upTo: maxMatchIndex

The first we need to do is to distinguish whether the traversal has arrived at an interesting attractor or not. Fortunately, we can easily adapt the checks we performed when the traversal was going backwards. In a similar manner, we can reuse our strategy for letting the characters make a decision. Therefore we write:

[…]

[…] Things look decent so far. But you may have noticed that these objects are being extremely lazy. Where is the piece that actually does something?

Well, here it is. Let’s examine ordinary character receivers now. We are in the context of having just seen a star, and now we have two possible outcomes: either the receiver matches aCharacter, or it does not. If it does not, we cannot conclude anything because it may be that we need to go over a few characters to find a match. So what we need to do is try to skip one character in the string and try again by delegating the job back to the pattern. This is perfectly fine because we have just seen a star. The receiver knows this because it has received this message, not any other message.

[…]