For example, you can search for assignments of the value 1234 to a variable named myVar
with the following pattern:
This will find the assignments even if the code is formatted with whiteSpace or comments in between or both.
myVar := 1234
Likewise, you can search for all creations of three-element Arrays, using a constant array size,
with the pattern:
or, you can search for a particular message being sent to the Transcript with:
Array new:3
Try using the codeSearcher on some of your own code
(and place comments or whitespace into your code).
Compare the results against the results of simple text search operations.
Transcript showCR:'oops'
To allow for those to be specified in the search-code pattern, the search-syntax supports pattern variables (also called meta variables).
Each metaPattern-variable must begin with a ` (backquote) character.
This character was choosen, because it does not occur in normal smalltalk code.
It is a bit hard to type on non-us keyboards, though.
Immediately following the ` character, other characters can be entered to specify
what type of node this metaPattern-variable should match.
After all the special character have been entered, you must then enter a valid variable name.
(the matched code is actually bound to this name inside the searcher and later
used by the code-rewriter.)
The special characters currently supported are listed in the following table:
self).
For example:
`foo
matches any variable.
# (Literal)
For example:
matches any literal:
`#literal
#(...), #foo, 1,
nil, true and false etc.
@ (List)
When applied to a keyword in a message, it will match a list of keyword messages (i.e., any message send)
When applied with a statement character (see below), it will match a list of statements
For example:
matches list of temps
| `@Temps |
matches a (possibly empty) list of statements
`@.Statements
matches any message node, literal node or block node
`@object
matches any message sent to
foo `@message: `@args
foo.
Notice, that this can also be used for a partial selector.
Therefore:
matches any message.
`@receiver `@keyword: `@arg1
In contrast,
matches any 2-argument message,
and
`@receiver `keyword1: `@arg1 `keyword2: `@arg2
matches any message with at least 2 arguments, which starts with
`@receiver at: `@arg1 `@keyword: `@arg2
at:.
. (Statement)
For example:
matches a single statement
`.Statement
And:
matches a (possibly empty) list of statements.
`.@Statements
` (backquote) (Recurse Into)
`@object foo
matches a foo message sent to any expression on the outer level.
However, the code "self foo foo" would match only once (the outer message send expression).
In contrast,
also matches
``@object foo
foo sent to any object,
plus for each match found, it will look for more matches in the ``@object part.
Thus, this will match twice for the "self foo foo" example.
{ (Pattern Block)
RBProgramNode) is passed as argument to the block.
`{:node | node isVariable and: ['RB*' match: node name]}
matches any variable whose name starts with 'RB'.
This allows for almost unlimited flexibility in the match:
matches any add expression sent to any variable which starts with 'co'.
`{:node | node isVariable and: ['co*' match: node name]} add: `@arg
To match empty array constants use:
`{:node | node isLiteral
and: [ node value isArray
and:[ node value size == 0 ]]}
and to match a non-block expression:
`{:node | node isBlock not }
The block may also be specified as a 2-argument block.
In this case, the matching dictionary is passed as second argument, allowing the block to
refer to previous match results.
`someVariable
at: `#someLiteral
put: `{:node :matchDict |
node isLiteral
and:[ node value isString
and:[ node value = matchDict at:#someLiteral ]] }
`sel
^ `sel
AND specify that the search should be a "method-search",
by setting the "Method"-CheckBox field in the dialog.
Another, example is the following pattern, which searches for all 2-argument methods,
which return their second argument:
`selPart1: `arg1 `selPart2: `arg2
^ `arg2
`foo - matches any variable or pseudo-variable.
`#foo - matches any literal (incl. nil, true and false).
Array new:3
Search for Array instance creations with any constant size:
Array new: `#n
Search for Array instance creations where a variable specifies the size:
Array new: `v
Search for all Array instance creations (any expression as size):
Array new: `@e
at:put: being sent to the Smalltalk-global,
with a variables value as argument,
use
Smalltalk at:`key put:`val
the above does not match for literal values or expression values as argument(s).
Smalltalk at:`@key put:`@val
to even look into the argument and look for sends there too,
use:
Smalltalk at:``@key put:``@val
``@rec `@msg: ``@args
Notice the extra backquote, which is required to recurse into already matched
expressions (otherwise, "rec foo foo" would only be matched once, for the
outer foo-message)
This pattern can be used to find repeated sends of the same message, as in:
which will match typical search operations.
`e sort: [:`a :`b | `a `sel < `b `sel ]
``@rec on: ``@arg1 do: ``@arg2
and:
``@rec handle: ``@arg1 do: ``@arg2
or, for a particular exception class:
StreamError handle: ``@arg1 do: ``@arg2
Error handle: [ :``@args | ] do: ``@blk
and:
``@blk on: Error do: [ :``@args | ]
`@e
ifTrue: `{:node | node isBlock not }
ifFalse: `{:node | node isBlock not }
``@object not ifTrue: ``@block
and:
``@object not ifFalse: ``@block
are obviously easier written by negating the if-test message.
The following code-pieces check if some element is in a collection:
( ``@expr1 detect:[:`v | ``@expr2 ] ifNone:nil ) notNil
and should be written as:
( ``@expr1 detect:[:`v | ``@expr2 ] ifNone:[] ) notNil
More unclean uses of the collection protocol are:
( ``@expr1 contains:[:`v | ``@expr2 ] )
which can be replaced by:
`@coll do:[:`el|
`@condition ifTrue:[
^ true
]
]
and this pattern searches for "beginners code", which can be replaced by
a simpler and more descriptive
( `@coll contains[:`el| `@condition ] ) ifTrue:[ ^ true ]
"detect:ifNone:"-message:
More examples are:
`@coll do:[:`el|
`@condition ifTrue:[
^ `el
]
]
and:
(`@e1 contains:[:`v | `@e2 not])
might both be replaced by a
`@e1 do:[:`v| `@e2 ifFalse:[^ false] ].
#conform:-message.
`.duplicate
matches any statement.
`.duplicate.
`.duplicate
should match two identical consecutive statements that are the whole body
of a sequence node. However as soon as we get beyond a within-statement
expressions, we are matching sequence nodes. Matching two statements
within a sequence node therefore requires
`.@beforeStatements.
`.duplicate.
`.duplicate.
`.afterStatements
Because the . makes the tool build a sequence node, you must provide the
"zero or more statements before and after"-code, unlike the expression case
where it could match an expression within a longer expression.
| `@temps |
`.@beforeStatements.
`.duplicate.
`.duplicate.
`.afterStatements
which will match two duplicate statements within any sequence of
statements.
It can be tricky to match sequence nodes; Even Don (one of the original authors of the refactory code) admitted, that he usually took two or three goes to get his expression right.
One problem with the above is that the whole match is presented as search result; although you are usually only interrested in the duplicate statement(s).
Copyright © eXept Software AG, all rights reserved