|
2 | 2 |
|
3 | 3 | [](https://travis-ci.org/MikeInnes/MacroTools.jl)
|
4 | 4 |
|
5 |
| -This library provides helpful tools for writing macros, notably a very simple |
6 |
| -but powerful templating system and some functions that have proven useful to me (see |
7 |
| -[utils.jl](src/utils.jl).) |
8 |
| - |
9 |
| -## Template Matching |
10 |
| - |
11 |
| -Template matching enables macro writers to deconstruct Julia |
12 |
| -expressions in a more declarative way, and without having to know in |
13 |
| -great detail how syntax is represented internally. For example, say you |
14 |
| -have a type definition: |
15 |
| - |
16 |
| -```julia |
17 |
| -ex = quote |
18 |
| - struct Foo |
19 |
| - x::Int |
20 |
| - y |
21 |
| - end |
22 |
| -end |
23 |
| -``` |
24 |
| - |
25 |
| -If you know what you're doing, you can pull out the name and fields via: |
26 |
| - |
27 |
| -```julia |
28 |
| -julia> if isexpr(ex.args[2], :struct) |
29 |
| - (ex.args[2].args[2], ex.args[2].args[3].args) |
30 |
| - end |
31 |
| -(:Foo,{:( # line 3:),:(x::Int),:( # line 4:),:y}) |
32 |
| -``` |
33 |
| -
|
34 |
| -But this is hard to write – since you have to deconstruct the `type` |
35 |
| -expression by hand – and hard to read, since you can't tell at a glance |
36 |
| -what's being achieved. On top of that, there's a bunch of messy stuff to |
37 |
| -deal with like pesky `begin` blocks which wrap a single expression, line |
38 |
| -numbers, etc. etc. |
39 |
| -
|
40 |
| -Enter MacroTools: |
41 |
| -
|
42 |
| -```julia |
43 |
| -julia> using MacroTools |
44 |
| - |
45 |
| -julia> @capture(ex, struct T_ fields__ end) |
46 |
| -true |
47 |
| - |
48 |
| -julia> T, fields |
49 |
| -(:Foo, [:(x::Int), :y]) |
50 |
| -``` |
51 |
| -
|
52 |
| -Symbols like `T_` underscore are treated as catchalls which match any |
53 |
| -expression, and the expression they match is bound to the |
54 |
| -(underscore-less) variable, as above. |
55 |
| -
|
56 |
| -Because `@capture` doubles as a test as well as extracting values, you can |
57 |
| -easily handle unexpected input (try writing this by hand): |
58 |
| -
|
59 |
| -```julia |
60 |
| -@capture(ex, f_{T_}(xs__) = body_) || |
61 |
| - error("expected a function with a single type parameter") |
62 |
| -``` |
63 |
| -
|
64 |
| -Symbols like `f__` (double underscored) are similar, but slurp a sequence of |
65 |
| -arguments into an array. For example: |
66 |
| -
|
67 |
| -```julia |
68 |
| -julia> @capture(:[1, 2, 3, 4, 5, 6, 7], [1, a_, 3, b__, c_]) |
69 |
| -true |
70 |
| - |
71 |
| -julia> a, b, c |
72 |
| -(2,[4,5,6],7) |
73 |
| -``` |
74 |
| -
|
75 |
| -Slurps don't have to be at the end of an expression, but like the |
76 |
| -Highlander there can only be one (per expression). |
77 |
| -
|
78 |
| -### Matching on expression type |
79 |
| -
|
80 |
| -`@capture` can match expressions by their type, which is either the `head` of `Expr` |
81 |
| -objects or the `typeof` atomic stuff like `Symbol`s and `Int`s. For example: |
82 |
| -
|
83 |
| -```julia |
84 |
| -@capture(ex, foo(x_String_string)) |
85 |
| -``` |
86 |
| -
|
87 |
| -This will match a call to the `foo` function which has a single argument, which |
88 |
| -may either be a `String` object or a `Expr(:string, ...)` |
89 |
| -(e.g. `@capture(:(foo("$(a)")), foo(x_String_string))`). Julia string literals |
90 |
| -may be parsed into either type of object, so this is a handy way to catch both. |
91 |
| -
|
92 |
| -Another common use case is to catch symbol literals, e.g. |
93 |
| -
|
94 |
| -```julia |
95 |
| -@capture(ex, |
96 |
| - struct T_Symbol |
97 |
| - fields__ |
98 |
| - end) |
99 |
| -``` |
100 |
| -
|
101 |
| -which will match e.g. `struct Foo ...` but not `struct Foo{V} ...` |
102 |
| -
|
103 |
| -### Unions |
104 |
| -
|
105 |
| -`@capture` can also try to match the expression against one pattern or another, |
106 |
| -for example: |
107 |
| -
|
108 |
| -```julia |
109 |
| -@capture(ex, f_(args__) = body_ | function f_(args__) body_ end) |
110 |
| -``` |
111 |
| -
|
112 |
| -will match both kinds of function syntax (though it's easier to use |
113 |
| -`shortdef` to normalise definitions). You can also do this within |
114 |
| -expressions, e.g. |
115 |
| -
|
116 |
| -```julia |
117 |
| -@capture(ex, (f_{T_}|f_)(args__) = body_) |
118 |
| -``` |
119 |
| -
|
120 |
| -matches a function definition, with a single type parameter bound to `T` if possible. |
121 |
| -If not, `T = nothing`. |
122 |
| -
|
123 |
| -## Expression Walking |
124 |
| -
|
125 |
| -If you've ever written any more interesting macros, you've probably found |
126 |
| -yourself writing recursive functions to work with nested `Expr` trees. |
127 |
| -MacroTools' `prewalk` and `postwalk` functions factor out the recursion, making |
128 |
| -macro code much more concise and robust. |
129 |
| -
|
130 |
| -These expression-walking functions essentially provide a kind of |
131 |
| -find-and-replace for expression trees. For example: |
132 |
| -
|
133 |
| -```julia |
134 |
| -julia> using MacroTools: prewalk, postwalk |
135 |
| - |
136 |
| -julia> postwalk(x -> x isa Integer ? x + 1 : x, :(2+3)) |
137 |
| -:(3 + 4) |
138 |
| -``` |
139 |
| -
|
140 |
| -In other words, look at each item in the tree; if it's an integer, add one, if not, leave it alone. |
141 |
| -
|
142 |
| -We can do more complex things if we combine this with `@capture`. For example, say we want to insert an extra argument into all function calls: |
143 |
| -
|
144 |
| -```julia |
145 |
| -julia> ex = quote |
146 |
| - x = f(y, g(z)) |
147 |
| - return h(x) |
148 |
| - end |
149 |
| - |
150 |
| -julia> postwalk(x -> @capture(x, f_(xs__)) ? :($f(5, $(xs...))) : x, ex) |
151 |
| -quote # REPL[20], line 2: |
152 |
| - x = f(5, y, g(5, z)) # REPL[20], line 3: |
153 |
| - return h(5, x) |
154 |
| -end |
155 |
| -``` |
156 |
| -
|
157 |
| -Most of the time, you can use `postwalk` without worrying about it, but we also |
158 |
| -provide `prewalk`. The difference is the order in which you see sub-expressions; |
159 |
| -`postwalk` sees the leaves of the `Expr` tree first and the whole expression |
160 |
| -last, while `prewalk` is the opposite. |
161 |
| -
|
162 |
| -```julia |
163 |
| -julia> postwalk(x -> @show(x) isa Integer ? x + 1 : x, :(2+3*4)); |
164 |
| -x = :+ |
165 |
| -x = 2 |
166 |
| -x = :* |
167 |
| -x = 3 |
168 |
| -x = 4 |
169 |
| -x = :(4 * 5) |
170 |
| -x = :(3 + 4 * 5) |
171 |
| - |
172 |
| -julia> prewalk(x -> @show(x) isa Integer ? x + 1 : x, :(2+3*4)); |
173 |
| -x = :(2 + 3 * 4) |
174 |
| -x = :+ |
175 |
| -x = 2 |
176 |
| -x = :(3 * 4) |
177 |
| -x = :* |
178 |
| -x = 3 |
179 |
| -x = 4 |
180 |
| -``` |
181 |
| -
|
182 |
| -A significant difference is that `prewalk` will walk into whatever expression |
183 |
| -you return. |
184 |
| -
|
185 |
| -```julia |
186 |
| -julia> postwalk(x -> @show(x) isa Integer ? :(a+b) : x, 2) |
187 |
| -x = 2 |
188 |
| -:(a + b) |
189 |
| - |
190 |
| -julia> prewalk(x -> @show(x) isa Integer ? :(a+b) : x, 2) |
191 |
| -x = 2 |
192 |
| -x = :+ |
193 |
| -x = :a |
194 |
| -x = :b |
195 |
| -:(a + b) |
196 |
| -``` |
197 |
| -
|
198 |
| -This makes it somewhat more prone to infinite loops; for example, if we returned |
199 |
| -`:(1+b)` instead of `:(a+b)`, `prewalk` would hang trying to expand all of the |
200 |
| -`1`s in the expression. |
201 |
| -
|
202 |
| -With these tools in hand, a useful general pattern for macros is: |
203 |
| -
|
204 |
| -```julia |
205 |
| -macro foo(ex) |
206 |
| - postwalk(ex) do x |
207 |
| - @capture(x, some_pattern) || return x |
208 |
| - return new_x |
209 |
| - end |
210 |
| -end |
211 |
| -``` |
212 |
| -
|
213 |
| -## Function definitions |
214 |
| -
|
215 |
| -`splitdef(def)` matches a function definition of the form |
216 |
| -
|
217 |
| -```julia |
218 |
| -function name{params}(args; kwargs)::rtype where {whereparams} |
219 |
| - body |
220 |
| -end |
221 |
| -``` |
222 |
| -
|
223 |
| -and returns `Dict(:name=>..., :args=>..., etc.)`. The definition can be rebuilt by |
224 |
| -calling `MacroTools.combinedef(dict)`, or explicitly with |
225 |
| -
|
226 |
| -```julia |
227 |
| -rtype = get(dict, :rtype, :Any) |
228 |
| -all_params = [get(dict, :params, [])..., get(dict, :whereparams, [])...] |
229 |
| -:(function $(dict[:name]){$(all_params...)}($(dict[:args]...); |
230 |
| - $(dict[:kwargs]...))::$rtype |
231 |
| - $(dict[:body]) |
232 |
| - end) |
233 |
| -``` |
234 |
| -
|
235 |
| -`splitarg(arg)` matches function arguments (whether from a definition or a function call) |
236 |
| -such as `x::Int=2` and returns `(arg_name, arg_type, slurp, default)`. `default` is |
237 |
| -`nothing` when there is none. For example: |
238 |
| -
|
239 |
| -```julia |
240 |
| -> map(splitarg, (:(f(y, a=2, x::Int=nothing, args...))).args[2:end]) |
241 |
| -4-element Array{Tuple{Symbol,Symbol,Bool,Any},1}: |
242 |
| - (:y, :Any, false, nothing) |
243 |
| - (:a, :Any, false, 2) |
244 |
| - (:x, :Int, false, :nothing) |
245 |
| - (:args, :Any, true, nothing) |
246 |
| -``` |
| 5 | +MacroTools provides a library of tools for working with Julia code and expressions. This includes a powerful template-matching system and code-walking tools that let you do deep transformations of code in a few lines. See the [docs](http://mikeinnes.github.io/MacroTools.jl/) for more info. |
0 commit comments