~ubuntu-branches/ubuntu/quantal/python-peak.rules/quantal

« back to all changes in this revision

Viewing changes to rules/Syntax-Matching.txt

  • Committer: Package Import Robot
  • Author(s): Barry Warsaw
  • Date: 2011-10-21 18:51:25 UTC
  • mfrom: (0.8.1) (0.7.1) (1.4.1) (4.1.2 precise)
  • Revision ID: package-import@ubuntu.com-20111021185125-tasydfgcvgc6ynyh
Tags: 0.5a1+r2707-1fakesync1
Fake sync due to mismatching orig tarball.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
======================
 
2
Matching Python Syntax
 
3
======================
 
4
 
 
5
The ``peak.rules.syntax`` module allows you to define pattern-matching
 
6
predicates against snippets of parameterized Python code, such that a
 
7
rule expression like::
 
8
 
 
9
    syntax.match(expr, type(`x`) is `y`) and isinstance(y, Const)
 
10
 
 
11
Will return true if ``expr`` is a PEAK-Rules AST of the form::
 
12
 
 
13
    Compare(Call(Const(type), (v1,)), ('is', Const(v2)))
 
14
 
 
15
(where v1 and v2 are arbitrary values).
 
16
 
 
17
 
 
18
.. contents:: **Table of Contents**
 
19
 
 
20
 
 
21
Bind Variables
 
22
==============
 
23
 
 
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
 
29
above.)
 
30
 
 
31
Bind variables are represented within a tree as an AST node created from the
 
32
variable name::
 
33
 
 
34
    >>> from peak.rules.syntax import Bind
 
35
 
 
36
    >>> Bind('x')
 
37
    Bind('x')
 
38
 
 
39
 
 
40
Compiling Tree-Match Predicates
 
41
===============================
 
42
 
 
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::
 
48
 
 
49
    >>> from peak.rules.syntax import match_predicate
 
50
 
 
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::
 
55
 
 
56
    >>> from peak.util.assembler import Local
 
57
    >>> b = {}
 
58
 
 
59
    >>> match_predicate(Bind('x'), Local('y'), b)
 
60
    True
 
61
 
 
62
    >>> b
 
63
    {'x': [Local('y')]}
 
64
 
 
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
 
68
updated::
 
69
 
 
70
    >>> match_predicate(Bind('x'), Local('z'), b)
 
71
    Test(Truth(Compare(Local('z'), (('==', Local('y')),))), True)
 
72
 
 
73
 
 
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::
 
78
 
 
79
    >>> match_predicate(Bind('x'), Local('z'), b)
 
80
    True
 
81
 
 
82
    >>> b
 
83
    {'x': [Local('y'), Local('z')]}
 
84
 
 
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::
 
89
 
 
90
    >>> any = Bind('_')
 
91
    >>> match_predicate(any, Local('q'), b)
 
92
    True
 
93
 
 
94
    >>> b
 
95
    {'x': [Local('y'), Local('z')]}
 
96
 
 
97
 
 
98
Matching Structures and AST nodes
 
99
---------------------------------
 
100
 
 
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::
 
105
 
 
106
    >>> b = {}
 
107
    >>> from peak.util.assembler import *
 
108
    >>> from peak.rules.codegen import *
 
109
 
 
110
    >>> match_predicate(Add(any, any), Local('q'), b)
 
111
    Test(IsInstance(Local('q')), istype(<class '...Add'>, True))
 
112
 
 
113
    >>> b
 
114
    {}
 
115
 
 
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::
 
118
 
 
119
    >>> match_predicate(Add(Bind('x'), Bind('y')), Local('q'), b)
 
120
    Test(IsInstance(Local('q')), istype(<class '...Add'>, True))
 
121
 
 
122
    >>> b
 
123
    {'y': [Getitem(Local('q'), Const(2))],
 
124
     'x': [Getitem(Local('q'), Const(1))]}
 
125
 
 
126
Non-node patterns are treated as equality comparisons::
 
127
 
 
128
    >>> b = {}
 
129
    >>> match_predicate(42, Local('q'), b)
 
130
    Test(Comparison(Local('q')), Value(42, True))
 
131
 
 
132
    >>> b
 
133
    {}
 
134
 
 
135
Except for ``None``, which produces an ``is None`` test::
 
136
 
 
137
    >>> match_predicate(None, Local('q'), b)
 
138
    Test(Identity(Local('q')), IsObject(None, True))
 
139
 
 
140
    >>> b
 
141
    {}
 
142
 
 
143
And sequences are matched by comparing their length::
 
144
 
 
145
    >>> match_predicate((), Local('q'), b)
 
146
    Test(Comparison(Call(Const(<... len>), (Local('q'),),...)), Value(0, True))
 
147
 
 
148
    >>> match_predicate([], Local('q'), b)
 
149
    Test(Comparison(Call(Const(<... len>), (Local('q'),),...)), Value(0, True))
 
150
 
 
151
    >>> b
 
152
    {}
 
153
 
 
154
And recursively matching their contents::
 
155
 
 
156
    >>> match_predicate((Bind('x'), Add(Bind('y'), any)), Local('q'), b)
 
157
    Signature([Test(Comparison(Call(Const(<... len>), (Local('q'),),...)),
 
158
                    Value(2, True)),
 
159
               Test(IsInstance(Getitem(Local('q'), Const(1))),
 
160
                    istype(<class '...Add'>, True))])
 
161
 
 
162
    >>> b
 
163
    {'y': [Getitem(Getitem(Local('q'), Const(1)), Const(1))],
 
164
     'x': [Getitem(Local('q'), Const(0))]}
 
165
 
 
166
 
 
167
Parsing Syntax Patterns
 
168
=======================
 
169
 
 
170
The ``syntax.SyntaxBuilder`` class is used to parse Python expressions into
 
171
AST patterns suitable for use with ``match_predicate``::
 
172
 
 
173
    >>> from peak.rules.syntax import SyntaxBuilder, match
 
174
    >>> builder = SyntaxBuilder({}, locals(), globals(), __builtins__)
 
175
    >>> pe = builder.parse
 
176
 
 
177
It parses backquoted identifiers into ``Bind`` nodes:
 
178
 
 
179
    >>> pe('type(`x`) is `y`')
 
180
    Compare(Call(Const(<type 'type'>), (Bind('x'),), (), (), (), True),
 
181
            (('is', Bind('y')),))
 
182
 
 
183
And rejects all other use of backquotes::
 
184
 
 
185
    >>> pe('`type(x)`')
 
186
    Traceback (most recent call last):
 
187
      ...
 
188
    SyntaxError: backquotes may only be used around an indentifier
 
189
 
 
190
In all other respects, it's essentially the same as ``codegen.ExprBuilder``.
 
191
 
 
192
 
 
193
The ``match()`` Pseudo-function
 
194
-------------------------------
 
195
 
 
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.
 
202
 
 
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
 
205
expressions::
 
206
 
 
207
    >>> from peak.rules.syntax import match
 
208
 
 
209
    >>> from peak.rules.predicates import CriteriaBuilder
 
210
    >>> builder = CriteriaBuilder(
 
211
    ...     {'expr':Local('expr')}, locals(), globals(), __builtins__
 
212
    ... )
 
213
    >>> pe = builder.parse
 
214
 
 
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)),
 
221
                                       Const(1))),
 
222
                    Value(Const(<type 'type'>), True)),
 
223
               Test(Comparison(Call(Const(<... len>),
 
224
                                    (Getitem(Getitem(Local('expr'),
 
225
                                             Const(1)), Const(2)),), (),
 
226
                                    (), (), True)),
 
227
                               Value(1, True)),
 
228
               Test(Comparison(Call(Const(<... len>),
 
229
                                    (Getitem(Getitem(Local('expr'), Const(1)),
 
230
                                             Const(3)),), (), (), (), True)),
 
231
                               Value(0, True)),
 
232
               Test(Comparison(Call(Const(<... len>),
 
233
                                    (Getitem(Getitem(Local('expr'), Const(1)),
 
234
                                             Const(4)),), (), (), (), True)),
 
235
                               Value(0, True)),
 
236
               Test(Comparison(Call(Const(<... len>),
 
237
                                    (Getitem(Getitem(Local('expr'), Const(1)),
 
238
                                             Const(5)),), (), (), (), True)),
 
239
                               Value(0, True)),
 
240
               Test(Comparison(Getitem(Getitem(Local('expr'), Const(1)),
 
241
                                       Const(6))),
 
242
                               Value(True, True)),
 
243
               Test(Comparison(Call(Const(<... len>),
 
244
                                    (Getitem(Local('expr'), Const(2)),), (),
 
245
                                    (), (), True)),
 
246
                               Value(1, True)),
 
247
               Test(Comparison(Call(Const(<... len>),
 
248
                                    (Getitem(Getitem(Local('expr'), Const(2)),
 
249
                                             Const(0)),), (), (), (), True)),
 
250
                               Value(2, True)),
 
251
               Test(Comparison(Getitem(Getitem(Getitem(Local('expr'),
 
252
                                                       Const(2)), Const(0)),
 
253
                                       Const(0))),
 
254
                               Value('is', True))])
 
255
 
 
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))}
 
259