Pharo Smalltalk:BPatterns:使用Smalltalk风格的重写引擎
Working on Pharo Smalltalk: BPatterns: Rewrite Engine with Smalltalk Style

原始链接: http://dionisiydk.blogspot.com/2026/02/bpatterns-rewrite-engine-with-smalltalk.html

## BPatterns:Smalltalk 的更易用重写引擎 Smalltalk 重写引擎是一种强大的 AST 级别匹配和重写工具,于 1997 年引入,但其语法复杂且容易遗忘。这使得利用其功能变得困难,即使对于经验丰富的开发者也是如此。BPatterns 旨在通过提供基于现有引擎的流畅、Smalltalk 原生 API 来解决这个问题。 BPatterns 不使用晦涩的字符串,而是使用普通的 Smalltalk 代码块作为模式。定义所需结构的块通过 `#bpattern` 消息转换为模式。通配符由以“any”开头的变量表示。这种方法允许开发者直接在模式中使用标准的 Smalltalk 工具——代码补全、导航和重构。 BPatterns 通过 `#with:` 消息提供精化,可以使用标准的 Smalltalk 代码,甚至使用任意代码块进行复杂条件筛选。示例包括重命名或交换消息关键字,以及查找特定类型的消息。 虽然 BPatterns 还不是一个完整的封装器,但它已经支持许多重写引擎的功能,包括完整的 method 模式。它在可用性方面有了显著提高,使得重写引擎的强大功能无需“语法税”即可访问。进一步的开发包括一个简化的弃用 API。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 正在使用 Pharo Smalltalk:BPatterns:Smalltalk 风格的重写引擎 (dionisiydk.blogspot.com) 5 分,mpweiher 发表于 1 小时前 | 隐藏 | 过去 | 收藏 | 讨论 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

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: ``@nilBlock

Or maybe with single backticks?

`@receiver isNil ifTrue: `@nilBlock -> `@receiver ifNil: `@nilBlock

In 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: `@arg2

You can rename keywords using it:

`@receiver newKeywordPart: `@arg1 staticPart: `@arg2

Or even swap them:

`@receiver staticPart: `@arg2 `anyKeywordPart: `@arg1

It’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 ] bpattern

BPatterns

BPatterns provide a fluent, Smalltalk-native API on top of the rewrite engine, using ordinary Smalltalk blocks as patterns.

You create a BPattern instance by sending the #bpattern message to a block. The variables and selectors inside the block define the pattern to be matched against target AST nodes. By convention anything started with any word acts as a wildcard. Everything else must match structurally.

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.

To find all matching methods:

[ anyRcv isNil ifTrue: anyBlock ] bpattern browseUsers

To 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:

联系我们 contact @ memedata.com