|
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