5
The ``peak.rules.syntax`` module allows you to define pattern-matching
6
predicates against snippets of parameterized Python code, such that a
9
syntax.match(expr, type(`x`) is `y`) and isinstance(y, Const)
11
Will return true if ``expr`` is a PEAK-Rules AST of the form::
13
Compare(Call(Const(type), (v1,)), ('is', Const(v2)))
15
(where v1 and v2 are arbitrary values).
18
.. contents:: **Table of Contents**
24
Bind variables are placeholders in a pattern that "bind" themselves to the
25
value found in that location in the matched data structure. Thus, in the
26
example above, ```x``` and ```y``` are bind variables, and cause "y"
27
in the later part of the expression to refer to the right-hand side of the
28
``is`` operator being matched. (The arbitrary value ``v2`` in the example
31
Bind variables are represented within a tree as an AST node created from the
34
>>> from peak.rules.syntax import Bind
40
Compiling Tree-Match Predicates
41
===============================
43
The ``match_predicate(pattern, expr, binds)`` function is used to combine a
44
pattern AST and an expression AST to create a PEAK-Rules predicate object that
45
will match the specified pattern. The `binds` argument is a dictionary mapping
46
from bind-variable names to lists of expression ASTs, and is modified in-place
47
as the predicate is assembled::
49
>>> from peak.rules.syntax import match_predicate
51
Rules defined for this function will determine what to do based on the type of
52
`pattern`. If `pattern` is a bind variable, the `binds` dictionary is updated
53
in-place, inserting `expr` under the bind variable's name, and ``True`` is
54
returned, indicating that this part of the pattern will always match::
56
>>> from peak.util.assembler import Local
59
>>> match_predicate(Bind('x'), Local('y'), b)
65
If there's already an entry for that variable in the `binds` dictionary, a more
66
complex predicate is returned, performing an equality comparison between the
67
new binding and the old binding of the variable, and the value in `binds` is
70
>>> match_predicate(Bind('x'), Local('z'), b)
71
Test(Truth(Compare(Local('z'), (('==', Local('y')),))), True)
74
This is so that patterns like "`x` is not `x`" will actually compare the two
75
"x"s and see if they're equal. Of course, if you bind the same variable more
76
than once to equal expression ASTs, you will not get back a comparison, and
77
the `binds` will be unchanged::
79
>>> match_predicate(Bind('x'), Local('z'), b)
83
{'x': [Local('y'), Local('z')]}
85
Finally, there is a special exception for bind variables named ```_```: that
86
is, a single underscore. Bind variables of this name are never stored in the
87
`binds`, and always return ``True`` as a predicate, allowing you to use them as
88
"don't care" placeholders::
91
>>> match_predicate(any, Local('q'), b)
95
{'x': [Local('y'), Local('z')]}
98
Matching Structures and AST nodes
99
---------------------------------
101
For most node types other than ``Bind``, the predicates are a bit more complex.
102
By default, the predicate should be an exact (``istype``) match of the node
103
type, intersected with a recursive application of ``match_predicate()`` to each
104
of the target node's children. For example::
107
>>> from peak.util.assembler import *
108
>>> from peak.rules.codegen import *
110
>>> match_predicate(Add(any, any), Local('q'), b)
111
Test(IsInstance(Local('q')), istype(<class '...Add'>, True))
116
Each child is defined via a ``Getitem()`` operation on the target node, so that
117
any placeholders and criteria will target the right part of the tree::
119
>>> match_predicate(Add(Bind('x'), Bind('y')), Local('q'), b)
120
Test(IsInstance(Local('q')), istype(<class '...Add'>, True))
123
{'y': [Getitem(Local('q'), Const(2))],
124
'x': [Getitem(Local('q'), Const(1))]}
126
Non-node patterns are treated as equality comparisons::
129
>>> match_predicate(42, Local('q'), b)
130
Test(Comparison(Local('q')), Value(42, True))
135
Except for ``None``, which produces an ``is None`` test::
137
>>> match_predicate(None, Local('q'), b)
138
Test(Identity(Local('q')), IsObject(None, True))
143
And sequences are matched by comparing their length::
145
>>> match_predicate((), Local('q'), b)
146
Test(Comparison(Call(Const(<... len>), (Local('q'),),...)), Value(0, True))
148
>>> match_predicate([], Local('q'), b)
149
Test(Comparison(Call(Const(<... len>), (Local('q'),),...)), Value(0, True))
154
And recursively matching their contents::
156
>>> match_predicate((Bind('x'), Add(Bind('y'), any)), Local('q'), b)
157
Signature([Test(Comparison(Call(Const(<... len>), (Local('q'),),...)),
159
Test(IsInstance(Getitem(Local('q'), Const(1))),
160
istype(<class '...Add'>, True))])
163
{'y': [Getitem(Getitem(Local('q'), Const(1)), Const(1))],
164
'x': [Getitem(Local('q'), Const(0))]}
167
Parsing Syntax Patterns
168
=======================
170
The ``syntax.SyntaxBuilder`` class is used to parse Python expressions into
171
AST patterns suitable for use with ``match_predicate``::
173
>>> from peak.rules.syntax import SyntaxBuilder, match
174
>>> builder = SyntaxBuilder({}, locals(), globals(), __builtins__)
175
>>> pe = builder.parse
177
It parses backquoted identifiers into ``Bind`` nodes:
179
>>> pe('type(`x`) is `y`')
180
Compare(Call(Const(<type 'type'>), (Bind('x'),), (), (), (), True),
181
(('is', Bind('y')),))
183
And rejects all other use of backquotes::
186
Traceback (most recent call last):
188
SyntaxError: backquotes may only be used around an indentifier
190
In all other respects, it's essentially the same as ``codegen.ExprBuilder``.
193
The ``match()`` Pseudo-function
194
-------------------------------
196
This isn't really a function, but you can use it in a predicate string in order
197
to perform a pattern match on a PEAK-Rules AST. It's mainly intended for use
198
in extending PEAK-Rules to recognize and replace various kinds of subexpression
199
patterns (e.g. by adding rules to ``predicates.expressionSignature()``), but it
200
can of course also be used in any other tools you build atop PEAK-Rules'
201
expression machinery.
203
In this example, we show it being used to define a rule that will recognize
204
expressions of the form ``"type(x) is y"``, where x and y are arbitrary
207
>>> from peak.rules.syntax import match
209
>>> from peak.rules.predicates import CriteriaBuilder
210
>>> builder = CriteriaBuilder(
211
... {'expr':Local('expr')}, locals(), globals(), __builtins__
213
>>> pe = builder.parse
215
>>> pe('match(expr, type(`x`) is `y`)')
216
Signature([Test(IsInstance(Local('expr')),
217
istype(<class 'peak.util.assembler.Compare'>, True)),
218
Test(IsInstance(Getitem(Local('expr'), Const(1))),
219
istype(<class 'peak.util.assembler.Call'>, True)),
220
Test(Comparison(Getitem(Getitem(Local('expr'), Const(1)),
222
Value(Const(<type 'type'>), True)),
223
Test(Comparison(Call(Const(<... len>),
224
(Getitem(Getitem(Local('expr'),
225
Const(1)), Const(2)),), (),
228
Test(Comparison(Call(Const(<... len>),
229
(Getitem(Getitem(Local('expr'), Const(1)),
230
Const(3)),), (), (), (), True)),
232
Test(Comparison(Call(Const(<... len>),
233
(Getitem(Getitem(Local('expr'), Const(1)),
234
Const(4)),), (), (), (), True)),
236
Test(Comparison(Call(Const(<... len>),
237
(Getitem(Getitem(Local('expr'), Const(1)),
238
Const(5)),), (), (), (), True)),
240
Test(Comparison(Getitem(Getitem(Local('expr'), Const(1)),
243
Test(Comparison(Call(Const(<... len>),
244
(Getitem(Local('expr'), Const(2)),), (),
247
Test(Comparison(Call(Const(<... len>),
248
(Getitem(Getitem(Local('expr'), Const(2)),
249
Const(0)),), (), (), (), True)),
251
Test(Comparison(Getitem(Getitem(Getitem(Local('expr'),
252
Const(2)), Const(0)),
256
>>> builder.bindings[0]
257
{'y': Getitem(Getitem(Getitem(Local('expr'), Const(2)), Const(0)), Const(1)),
258
'x': Getitem(Getitem(Getitem(Local('expr'), Const(1)), Const(2)), Const(0))}