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