BPatterns

Description

Block based API for Smalltalk rewrite engine

Details

Source
GitHub
License
MIT
Stars
5
Forks
1
Created
April 24, 2025
Updated
Feb. 28, 2026

Categories

UI / Graphics Games

README excerpt

# BPatterns

[![GitHub release](https://img.shields.io/github/release/dionisiydk/BPatterns.svg)](https://github.com/dionisiydk/BPatterns/releases/latest)
[![Unit Tests](https://github.com/dionisiydk/BPatterns/actions/workflows/tests.yml/badge.svg)](https://github.com/dionisiydk/BPatterns/actions/workflows/tests.yml)
[![Pharo 13](https://img.shields.io/badge/Pharo-13-informational)](https://pharo.org)
[![Pharo 14-alpha](https://img.shields.io/badge/Pharo-alpha-informational)](https://pharo.org)

Scripting tool to search and rewrite the system using simple code examples defined by blocks (**B** is for block). No special syntax required.

It is based on the original Smalltalk rewrite engine invented by John Brant and Don Roberts in their work on the Refactoring Browser (see *“A Refactoring Tool for Smalltalk”, 1997*). 

## Installation

```Smalltalk
Metacello new
  baseline: 'BPatterns';
  repository: 'github://dionisiydk/BPatterns:main';
  load
```

## Overview

BPatterns can be created using **#bpattern** message to a pattern block:

```Smalltalk
	| any any2 |
	[ any isNil ifTrue: any2 ] bpattern
```

Once you have a `BPattern` you can browse the system to find all matching methods:

```Smalltalk
	| any any2 |
	[ any isNil ifTrue: any2 ] bpattern browseUsers.
```

<img width="1095" height="464" alt="Screenshot 2026-01-08 at 18 43 10" src="https://github.com/user-attachments/assets/3a392fdc-a34c-470f-970b-5c23e85a869d" />

Or you can scope it for a single class:

```Smalltalk
	| any |
	[ any printString asString ] bpattern browseUsersInClass: BPatternMethodQueryTest.
```

<img width="1033" height="313" alt="Screenshot 2026-01-08 at 18 44 34" src="https://github.com/user-attachments/assets/b39068f8-dec0-457d-8e65-c7bd45df64c6" />

Using two BPatterns you can rewrite the matching methods:

```Smalltalk
	| any |
	[ 
		[ any printString asString ] -> [ any printString ] 
	] brewrite previewForClass: BPatternMethodQueryTest
```

<img width="697" height="498" alt="Screenshot 2026-01-08 at 21 19 43" src="https://github.com/user-attachments/assets/df2fca06-aa6e-46e4-8d45-432492d95af0" />

See `BPatternRewrite` and `BPatternRewrite` for the implementation details.

## Patterns configuration

The variables and selectors inside the pattern block can be used as a pattern to match particular AST nodes.
By default the following objects are automatically configured as **ANY** pattern to match any AST node:
- variables started with **any** word
- selectors where a keyword is started with **any** word

To narrow the filter represented by a pattern you have to configure it using **#with:** message:

```Smalltalk
	| anyVar anyBlock |
	[ anyVar isNil ifTrue: anyBlock ] bpattern with: [ anyVar ] -> [:pattern | pattern beVariable ]
```

Blocks are used for variables to lexically reference them instead of using raw string names. 
Here the **#anyVar** pattern name will match only variables which are receivers of **#isNil** message.
For example it will match the following expression: 

```Smalltalk
 	instVar isNil ifTrue: [ anotherVar printString ]
``` 

But it will not match an expression where the receiver is an another message send:

```Smalltalk
 	instVar someMessage isNil ifTrue: [ anotherVar printString ]
``` 

See other config methods of `BPatternVariableNode` for other options.

By using such a configuration for non default objects they will be converted to the pattern:

```Smalltalk
	| someVar anyBlock |
	[ someVar isNil ifTrue: anyBlock ] bpattern with: [ someVar ] -> [:pattern | pattern beVariable ]
```

Without the config block the **#someVar** object would only match variables named **#someVar**.

The config block is optional and to enforce the pattern without extra settings you can just reference it:

```Smalltalk
	| someVar anyBlock |
	[ someVar isNil ifTrue: anyBlock ] bpattern with: [ someVar ]
```

In that case **#someVar** pattern will match **ANY** AST node like if it would be named **#anyVar**.

For the arguments of **#with:** message you can pass a block for variables, an association for a configuration or a symbol for a selector. And it can be an array of them:

```Smalltalk
	| any arg1 arg2 |
	[ any at: arg1 otherKeyword: arg2 ] bpattern 
		with: {[arg1. arg2] -> [:pattern | pattern beVariable]. #otherKeyword}
```

This example will match a message send with any receiver and variables as arguments and where a selector starts with **#at:** and an arbitrary second keyword (see **Selector Patterns**).

### Variable patterns

Patterns can be configured to match a particular type of variables:

```Smalltalk
	| anyVar anyBlock |
	[ anyVar do: anyBlock ] bpattern 
		with: [ anyVar ] -> [:pattern | pattern beInstVar ];
		with: [ anyBlock ] -> [:pattern | pattern beLocalVar ];
		browseUsers
```

<img width="1010" height="505" alt="Screenshot 2026-01-08 at 21 47 13" src="https://github.com/user-attachments/assets/36b863a8-dda3-4759-8a67-c686a08414e6" />


And you can specify an arbitrary block filter using **#where:** message:

```Smalltalk
	| anyVar anyBlock |
	[ anyVar do: anyBlock ] bpattern 
		with: [ anyVar ] -> [:pattern | pattern beInstVar ];
		with: [ anyBlock ] -> [:pattern | pattern beLocalVar where: [:var | 
				(var name beginsWith: 'a') not]];
		browseUsers
```

<img width="986" height="494" alt="Screenshot 2026-01-08 at 21 52 02" src="https://github.com/user-attachments/assets/60afe9f2-0fb0-4390-9120-b4c7451580d0" />

For other options see `BPatternVariableNode`. Few examples here:

### Global variable patterns

Patterns can find message sends to globals:

```Smalltalk
	| any |
	[ any initialize ] bpattern 
		with: [ any ] -> [:pattern | pattern beGlobalVar ];
		browseUsers
```

<img width="1017" height="551" alt="Screenshot 2026-01-08 at 21 54 42" src="https://github.com/user-attachments/assets/7a1b51ef-c7d2-4b0a-bb9e-9b7a660ccb9a" />

And you can narrow filter by given set of values:

```Smalltalk
	| any anySize |
	[ any new: anySize ] bpattern 
		with: [ any ] -> [:pattern | pattern beGlobalVarWithAny: {Array. Set} ];
		with: [ anySize] -> [:pattern | pattern beLiteral ];
		browseUsers
```

<img width="949" height="512" alt="Screenshot 2026-01-08 at 22 00 19" src="https://github.com/user-attachments/assets/3b32a99c-ea34-4cd5-9ae8-5bad0d79ee7a" />

### Undeclared variable patterns

Patterns can find undeclared variables:

```Smalltalk
	| any |
	[ any ] bpattern 
		with: [ any ] -> [:pattern | pattern beUndeclared ];
		browseUsers
```

<img width="984" height="499" alt="Screenshot 2026-01-08 at 22 03 32" src="https://github.com/user-attachments/assets/b3c61a94-d0fb-47b7-ac43-56af4ab9957e" />

### Literal patterns

Patterns can be configured to match literals:

```Smalltalk
	| any any2 |
	[ any + any2 ] bpattern 
		with: [ any. any2 ] -> [:pattern | pattern beLiteral ];
		browseUsers
```

This pattern will find all sum expressions with two literals.

<img width="899" height="493" alt="Screenshot 2026-01-08 at 22 05 23" src="https://github.com/user-attachments/assets/6d1e1efc-9cbd-427d-beb3-5acdb2700c8b" />

To narrow the filter you can add a **#where:** predicate block to match the literal values by an arbitrary criteria:

```Smalltalk
	| any any2 |
	[ any + any2 ] bpattern 
		with: [ any. any2 ] -> [:pattern | pattern beLiteral where: [:value | value isInteger not ]];
		browseUsers
```

<img width="1019" height="522" alt="Screenshot 2026-01-08 at 22 07 55" src="https://github.com/user-attachments/assets/ff3a3134-c9f4-4740-a386-6bd6cf4bc3f0" />

And you can also search for a particular list of literal values:

```Smalltalk
	| any any2 |
	[ any + any2 ] bpattern 
		with: [ any2 ] -> [:pattern | pattern beLiteralWithAny: #(2 3)];
		browseUsers
```

<img width="1007" height="544" alt="Screenshot 2026-01-08 at 22 10 20" src="https://github.com/user-attachments/assets/5e044e31-e567-49e9-a22d-616dd5aef403" />

### Selector patterns

Selectors can be also used as patterns:

```Smalltalk
	| any anyArg1 anyArg2 |
	[ any at: anyArg1 anyOtherKeyword: anyArg2 ] bpattern
```

It will match any message sends with a selector started with #at: and any other second keyword. It does not require any configuration because **#anyOtherKeyword:** is started with **any** word.  

The **keyword selector** pattern matches any type of selectors with same number of arguments.
For example the following pattern will match one argument keywords and any binary messages like 1 + 2:

```Smalltalk
	| any any2 |
	[ any anyMessage: any2 ] bpattern
```

To narrow the filter to the keyword type use **#beKeyword** config:

```Smalltalk
	| any any2 |
	[ any anyMessage: any2 ] bpattern with: #anyMessage: -> [:pattern | pattern beKeyword ].
```

To narrow the filter to the binary type use **#beBinary** config:

```Smalltalk
	| any any2 |
	[ any anyMessage: any2 ] bpattern with: #anyMessage: -> [:pattern | pattern beBinary ].
```

Or you can configure any binary selector as a pattern:

```Smalltalk
	| any any2 |
	[ any + any2 ] bpattern with: #+.
```

Both examples will match any binary message sends lile #+, #-, #=, etc..

To play a bit try to browse all binary messages where receiver and arguments are literals:

```Smalltalk
	| anyRcv anyArg |
	[ anyRcv + anyArg ] bpattern
		with: {#+. [ anyRcv. anyArg ] -> [:pattern | pattern beLiteral ]};
		browseUsers
```

<img width="1004" height="443" alt="Screenshot 2026-01-08 at 22 13 56" src="https://github.com/user-attachments/assets/52768459-bd0a-4239-b3a3-8847ab25c3d1" />

### Unary selector patterns

The unary patterns are special. If an unary selector begins with **any** word it will match any type of messages (unary, binary and keyword) with any number of arguments. No need to reference arguments explicitly:

```Smalltalk
	[ instVar anyMessage printString ] bpattern
```

It will match expressions like:

```Smalltalk
	(instVar at: #key) printString.
	instVar variable anotherVariable printString.
	(instVar + 1) printString.
```

Unary patterns are useful to describe an arbitrary message sends. For exampl
← Back to results