The rewrite engine is an absolutely brilliant invention by John Brant and Don Roberts, introduced with the Refactoring Browser (see “A Refactoring Tool for Smalltalk”, 1997). It gives us AST-level matching and rewriting with astonishing power.
But let’s be honest: how many people actually remember its syntax?
Even the simplest rewrite rule—say, replacing a deprecated message with a new one—usually sends me hunting for examples. During this project I spent a lot of time deep inside the rewrite engine, and even now I cannot reliably recall the exact syntax.
Is it something like this?
``@receiver isNil ifTrue: ``@nilBlock -> ``@receiver ifNil: ``@nilBlockOr maybe with single backticks?
`@receiver isNil ifTrue: `@nilBlock -> `@receiver ifNil: `@nilBlockIn fact, both versions work—but they apply different filters to the target node. Try to remember which one.
And that’s only the beginning.
Do you know you can wildcard parts of selectors?
`@receiver `anyKeywordPart: `@arg1 staticPart: `@arg2You can rename keywords using it:
`@receiver newKeywordPart: `@arg1 staticPart: `@arg2Or even swap them:
`@receiver staticPart: `@arg2 `anyKeywordPart: `@arg1It’s incredibly powerful. But how do you remember all of this?
With normal Smalltalk code, I would explore the system using senders, implementors, inspectors— gradually rebuilding my understanding. Here, that breaks down. The matching syntax lives inside strings, invisible to standard navigation tools. No code completion. No refactorings. No help from the environment.
So how do we keep the power without the syntax tax?
That is where BPatterns come in:
[ any isNil ifTrue: anyBlock ] bpatternBPatterns
BPatterns provide a fluent, Smalltalk-native API on top of the rewrite engine, using ordinary Smalltalk blocks as patterns.
Under the hood, BPattern builds a pattern AST using the same pattern node classes as the rewrite engine. All the original matching and rewriting machinery is still there — just wrapped in a more approachable, scriptable interface.
You can think of BPatterns as a Smalltalk DSL for the rewrite engine.
Pharo already provides dedicated tools for the rewrite engine, such as StRewriterMatchToolPresenter:
With BPatterns, none of that is required. A pattern is just a block. Add one more message and simple DoIt will do the job.
[ anyRcv isNil ifTrue: anyBlock ] bpattern browseUsersTo rewrite them:
[[ anyRcv isNil ifTrue: anyBlock ] -> [ anyRcv ifNil: anyBlock ]] brewrite preview
Refining Patterns Explicitly
You can narrow patterns explicitly using #with: message:
[ anyVar isNil ifTrue: anyBlock ] bpattern with: [ anyVar ] -> [:pattern | pattern beVariable ]
Because this is regular Smalltalk code, all standard development tools work out of the box: syntax highlighting, code completion, navigation, and refactorings:
Browse the implementors of #beVariable message and you will find other filters under BPatternVariableNode class, such as #beInstVar or #beLocalVar. If you miss something, just add a method. No new syntax required.
You can also use an arbitrary block as a filter:
[ anyVar isNil ifTrue: anyBlock ] bpattern
with: [ anyVar ] -> [:pattern |
pattern beInstVar where: [:var | var name beginsWith: 'somePrefix' ]]
Notice the block [anyVar] is used to reference variables where the configuration block should be applied. This avoids raw strings for variable names and keeps these configs friendly to development tools:
Message Patterns Revisited
Now let’s revisit the selector wildcard examples from the beginning using BPatterns.
Renaming a keyword:
[
[ anyRcv anyKeywordPart: anyArg1 staticPart: anyArg2 ]
-> [ anyRcv newKeywordPart: anyArg1 staticPart: anyArg2 ]
] brewrite.
Swapping keywords:
[
[ anyRcv anyKeywordPart: anyArg1 staticPart: anyArg2 ]
-> [ anyRcv staticPart: anyArg2 anyKeywordPart: anyArg1 ]
] brewrite.
Message patterns can also be refined using #with: message:
[ any anyMessage: any2 ] bpattern
with: #anyMessage: -> [:pattern | pattern beBinary ];
browseUsers.
This finds all methods containing binary messages:
Add another filter to keep only binaries between literals:
[ any anyMessage: any2 ] bpattern
with: #anyMessage: -> [:pattern | pattern beBinary ];
with: [ any. any2 ] -> [ :pattern | pattern beLiteral ];
browseUsers
The old syntax also supports literal patterns but good luck finding an example.
Message patterns can also be configured with arbitrary conditions:
[ any anyMessage ] bpattern
with: #anyMessage -> [:pattern | pattern where: [:node | node selector beginsWith: 'prim' ]];
browseUsers
Status and What’s Next
BPatterns don’t expose every feature of the rewrite engine yet, but many are already supported, including full method patterns via #bmethod.
For full details, see the GitHub repository:
And check the next blog post about a simplified deprecation API built on top of BPatterns:
No comments:
Post a Comment