1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
|
Term rewriting macros
=====================
Term rewriting macros are macros or templates that have not only
a *name* but also a *pattern* that is searched for after the semantic checking
phase of the compiler: This means they provide an easy way to enhance the
compilation pipeline with user defined optimizations:
.. code-block:: nim
template optMul{`*`(a, 2)}(a: int): int = a+a
let x = 3
echo x * 2
The compiler now rewrites ``x * 2`` as ``x + x``. The code inside the
curlies is the pattern to match against. The operators ``*``, ``**``,
``|``, ``~`` have a special meaning in patterns if they are written in infix
notation, so to match verbatim against ``*`` the ordinary function call syntax
needs to be used.
Unfortunately optimizations are hard to get right and even the tiny example
is **wrong**:
.. code-block:: nim
template optMul{`*`(a, 2)}(a: int): int = a+a
proc f(): int =
echo "side effect!"
result = 55
echo f() * 2
We cannot duplicate 'a' if it denotes an expression that has a side effect!
Fortunately Nim supports side effect analysis:
.. code-block:: nim
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a
proc f(): int =
echo "side effect!"
result = 55
echo f() * 2 # not optimized ;-)
So what about ``2 * a``? We should tell the compiler ``*`` is commutative. We
cannot really do that however as the following code only swaps arguments
blindly:
.. code-block:: nim
template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a
What optimizers really need to do is a *canonicalization*:
.. code-block:: nim
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a
The ``int{lit}`` parameter pattern matches against an expression of
type ``int``, but only if it's a literal.
Parameter constraints
---------------------
The `parameter constraint`:idx: expression can use the operators ``|`` (or),
``&`` (and) and ``~`` (not) and the following predicates:
=================== =====================================================
Predicate Meaning
=================== =====================================================
``atom`` The matching node has no children.
``lit`` The matching node is a literal like "abc", 12.
``sym`` The matching node must be a symbol (a bound
identifier).
``ident`` The matching node must be an identifier (an unbound
identifier).
``call`` The matching AST must be a call/apply expression.
``lvalue`` The matching AST must be an lvalue.
``sideeffect`` The matching AST must have a side effect.
``nosideeffect`` The matching AST must have no side effect.
``param`` A symbol which is a parameter.
``genericparam`` A symbol which is a generic parameter.
``module`` A symbol which is a module.
``type`` A symbol which is a type.
``var`` A symbol which is a variable.
``let`` A symbol which is a ``let`` variable.
``const`` A symbol which is a constant.
``result`` The special ``result`` variable.
``proc`` A symbol which is a proc.
``method`` A symbol which is a method.
``iterator`` A symbol which is an iterator.
``converter`` A symbol which is a converter.
``macro`` A symbol which is a macro.
``template`` A symbol which is a template.
``field`` A symbol which is a field in a tuple or an object.
``enumfield`` A symbol which is a field in an enumeration.
``forvar`` A for loop variable.
``label`` A label (used in ``block`` statements).
``nk*`` The matching AST must have the specified kind.
(Example: ``nkIfStmt`` denotes an ``if`` statement.)
``alias`` States that the marked parameter needs to alias
with *some* other parameter.
``noalias`` States that *every* other parameter must not alias
with the marked parameter.
=================== =====================================================
The ``alias`` and ``noalias`` predicates refer not only to the matching AST,
but also to every other bound parameter; syntactially they need to occur after
the ordinary AST predicates:
.. code-block:: nim
template ex{a = b + c}(a: int{noalias}, b, c: int) =
# this transformation is only valid if 'b' and 'c' do not alias 'a':
a = b
inc a, c
Pattern operators
-----------------
The operators ``*``, ``**``, ``|``, ``~`` have a special meaning in patterns
if they are written in infix notation.
The ``|`` operator
~~~~~~~~~~~~~~~~~~
The ``|`` operator if used as infix operator creates an ordered choice:
.. code-block:: nim
template t{0|1}(): expr = 3
let a = 1
# outputs 3:
echo a
The matching is performed after the compiler performed some optimizations like
constant folding, so the following does not work:
.. code-block:: nim
template t{0|1}(): expr = 3
# outputs 1:
echo 1
The reason is that the compiler already transformed the 1 into "1" for
the ``echo`` statement. However, a term rewriting macro should not change the
semantics anyway. In fact they can be deactived with the ``--patterns:off``
command line option or temporarily with the ``patterns`` pragma.
The ``{}`` operator
~~~~~~~~~~~~~~~~~~~
A pattern expression can be bound to a pattern parameter via the ``expr{param}``
notation:
.. code-block:: nim
template t{(0|1|2){x}}(x: expr): expr = x+1
let a = 1
# outputs 2:
echo a
The ``~`` operator
~~~~~~~~~~~~~~~~~~
The ``~`` operator is the **not** operator in patterns:
.. code-block:: nim
template t{x = (~x){y} and (~x){z}}(x, y, z: bool): stmt =
x = y
if x: x = z
var
a = false
b = true
c = false
a = b and c
echo a
The ``*`` operator
~~~~~~~~~~~~~~~~~~
The ``*`` operator can *flatten* a nested binary expression like ``a & b & c``
to ``&(a, b, c)``:
.. code-block:: nim
var
calls = 0
proc `&&`(s: varargs[string]): string =
result = s[0]
for i in 1..len(s)-1: result.add s[i]
inc calls
template optConc{ `&&` * a }(a: string): expr = &&a
let space = " "
echo "my" && (space & "awe" && "some " ) && "concat"
# check that it's been optimized properly:
doAssert calls == 1
The second operator of `*` must be a parameter; it is used to gather all the
arguments. The expression ``"my" && (space & "awe" && "some " ) && "concat"``
is passed to ``optConc`` in ``a`` as a special list (of kind ``nkArgList``)
which is flattened into a call expression; thus the invocation of ``optConc``
produces:
.. code-block:: nim
`&&`("my", space & "awe", "some ", "concat")
The ``**`` operator
~~~~~~~~~~~~~~~~~~~
The ``**`` is much like the ``*`` operator, except that it gathers not only
all the arguments, but also the matched operators in reverse polish notation:
.. code-block:: nim
import macros
type
Matrix = object
dummy: int
proc `*`(a, b: Matrix): Matrix = discard
proc `+`(a, b: Matrix): Matrix = discard
proc `-`(a, b: Matrix): Matrix = discard
proc `$`(a: Matrix): string = result = $a.dummy
proc mat21(): Matrix =
result.dummy = 21
macro optM{ (`+`|`-`|`*`) ** a }(a: Matrix): expr =
echo treeRepr(a)
result = newCall(bindSym"mat21")
var x, y, z: Matrix
echo x + y * z - x
This passes the expression ``x + y * z - x`` to the ``optM`` macro as
an ``nnkArgList`` node containing::
Arglist
Sym "x"
Sym "y"
Sym "z"
Sym "*"
Sym "+"
Sym "x"
Sym "-"
(Which is the reverse polish notation of ``x + y * z - x``.)
Parameters
----------
Parameters in a pattern are type checked in the matching process. If a
parameter is of the type ``varargs`` it is treated specially and it can match
0 or more arguments in the AST to be matched against:
.. code-block:: nim
template optWrite{
write(f, x)
((write|writeln){w})(f, y)
}(x, y: varargs[expr], f: File, w: expr) =
w(f, x, y)
Example: Partial evaluation
---------------------------
The following example shows how some simple partial evaluation can be
implemented with term rewriting:
.. code-block:: nim
proc p(x, y: int; cond: bool): int =
result = if cond: x + y else: x - y
template optP1{p(x, y, true)}(x, y: expr): expr = x + y
template optP2{p(x, y, false)}(x, y: expr): expr = x - y
Example: Hoisting
-----------------
The following example shows how some form of hoisting can be implemented:
.. code-block:: nim
import pegs
template optPeg{peg(pattern)}(pattern: string{lit}): Peg =
var gl {.global, gensym.} = peg(pattern)
gl
for i in 0 .. 3:
echo match("(a b c)", peg"'(' @ ')'")
echo match("W_HI_Le", peg"\y 'while'")
The ``optPeg`` template optimizes the case of a peg constructor with a string
literal, so that the pattern will only be parsed once at program startup and
stored in a global ``gl`` which is then re-used. This optimization is called
hoisting because it is comparable to classical loop hoisting.
AST based overloading
=====================
Parameter constraints can also be used for ordinary routine parameters; these
constraints affect ordinary overloading resolution then:
.. code-block:: nim
proc optLit(a: string{lit|`const`}) =
echo "string literal"
proc optLit(a: string) =
echo "no string literal"
const
constant = "abc"
var
variable = "xyz"
optLit("literal")
optLit(constant)
optLit(variable)
However, the constraints ``alias`` and ``noalias`` are not available in
ordinary routines.
Move optimization
-----------------
The ``call`` constraint is particularly useful to implement a move
optimization for types that have copying semantics:
.. code-block:: nim
proc `[]=`*(t: var Table, key: string, val: string) =
## puts a (key, value)-pair into `t`. The semantics of string require
## a copy here:
let idx = findInsertionPosition(key)
t[idx] = key
t[idx] = val
proc `[]=`*(t: var Table, key: string{call}, val: string{call}) =
## puts a (key, value)-pair into `t`. Optimized version that knows that
## the strings are unique and thus don't need to be copied:
let idx = findInsertionPosition(key)
shallowCopy t[idx], key
shallowCopy t[idx], val
var t: Table
# overloading resolution ensures that the optimized []= is called here:
t[f()] = g()
|