Skip to content

Commit a0c810e

Browse files
committed
sourcewalk docs
1 parent a0e1c6f commit a0c810e

File tree

5 files changed

+128
-2
lines changed

5 files changed

+128
-2
lines changed

docs/Manifest.toml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,26 @@
33
[[Base64]]
44
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
55

6+
[[CSTParser]]
7+
deps = ["LibGit2", "Test", "Tokenize"]
8+
git-tree-sha1 = "d878de3315f9b6569851d919f7976fe527d00c24"
9+
repo-rev = "location"
10+
repo-url = "https://github.com/MikeInnes/CSTParser.jl"
11+
uuid = "00ebfdb7-1f24-5e51-bd34-a7502290713f"
12+
version = "0.5.2+"
13+
614
[[Compat]]
715
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
816
git-tree-sha1 = "84aa74986c5b9b898b0d1acaf3258741ee64754f"
917
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
1018
version = "2.1.0"
1119

20+
[[DataStructures]]
21+
deps = ["InteractiveUtils", "OrderedCollections", "Random", "Serialization", "Test"]
22+
git-tree-sha1 = "ca971f03e146cf144a9e2f2ce59674f5bf0e8038"
23+
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
24+
version = "0.15.0"
25+
1226
[[Dates]]
1327
deps = ["Printf"]
1428
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -57,7 +71,7 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
5771
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
5872

5973
[[MacroTools]]
60-
deps = ["Compat"]
74+
deps = ["CSTParser", "Compat", "DataStructures", "Test"]
6175
path = ".."
6276
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
6377
version = "0.4.5+"
@@ -69,6 +83,12 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
6983
[[Mmap]]
7084
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
7185

86+
[[OrderedCollections]]
87+
deps = ["Random", "Serialization", "Test"]
88+
git-tree-sha1 = "85619a3f3e17bb4761fe1b1fd47f0e979f964d5b"
89+
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
90+
version = "1.0.2"
91+
7292
[[Pkg]]
7393
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
7494
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
@@ -110,6 +130,12 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
110130
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
111131
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
112132

133+
[[Tokenize]]
134+
deps = ["Printf", "Test"]
135+
git-tree-sha1 = "3e83f60b74911d3042d3550884ca2776386a02b8"
136+
uuid = "0796e94c-ce3b-5d07-9a54-7f471281c624"
137+
version = "0.5.3"
138+
113139
[[UUIDs]]
114140
deps = ["Random", "SHA"]
115141
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[deps]
2+
CSTParser = "00ebfdb7-1f24-5e51-bd34-a7502290713f"
23
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
34
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ makedocs(
55
pages = [
66
"Home" => "index.md",
77
"Pattern Matching" => "pattern-matching.md",
8+
"SourceWalk" => "sourcewalk.md",
89
"Utilities" => "utilities.md"],
910
format = Documenter.HTML(prettyurls = haskey(ENV, "CI")))
1011

docs/src/sourcewalk.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# SourceWalk
2+
3+
Pattern matching is really useful if you're writing macros, but it also works
4+
on Julia source code files. You can try this with the `sourcewalk` function,
5+
which behaves like `postwalk` but with a source file as input.
6+
7+
!!! note
8+
9+
To use this functionality you currently need to a fork of CSTParser.
10+
```julia
11+
] add https://github.com/MikeInnes/CSTParser.jl#location
12+
```
13+
14+
For example, if we have a file like
15+
16+
```julia
17+
# test.jl
18+
foo(a, b)
19+
```
20+
21+
And run
22+
23+
```julia
24+
sourcewalk("test.jl") do x
25+
x == :foo ? :bar : x
26+
end
27+
```
28+
29+
then our file will have changed to
30+
31+
```julia
32+
# test.jl
33+
foo(a, b)
34+
```
35+
36+
You can also pass `sourcewalk` a directory, and it will work on all `.jl` files
37+
in that directory. You can use `sourcewalk` anywhere you'd normally use a
38+
Regex-based find and replace, but it'll take care of subtle issues like matching
39+
brackets, operator precedence and code formatting.
40+
41+
We can use this to do some very powerful code transformations with minimal
42+
effort. For example, [this whole
43+
patch](https://github.com/MikeInnes/julia/commit/45ccbc6a3c003accb0eedca889835071c371ae86)
44+
was generated simply with `sourcewalk(longdef, "base")`. Let's look at some
45+
examples.
46+
47+
!!! warning
48+
49+
We recommend running SourceWalk on files that are checked into Git. This way
50+
you can easily look through every change and check it's sensible. Being
51+
careless with `sourcewalk` can delete all your code!
52+
53+
## Find and Replace
54+
55+
Here's a more realistic example. When working with strings, we use `nextind(s, i)`
56+
to get the next index after `i` – which will often just be `i+1`, but may
57+
be something else if we're working with unicode.
58+
59+
How do you know your code handles unicode correctly? One way to check is to
60+
actually replace `nextind(s, i)` with `i + 1` and _make sure that the tests
61+
fail_ – if they don't, we're not testing with unicode input properly. This is a
62+
form of [mutation testing](https://en.wikipedia.org/wiki/Mutation_testing).
63+
64+
Of course, the transformation is very simple with MacroTools:
65+
66+
```julia
67+
julia> function nextinds(x)
68+
@capture(x, nextind(str_, i_)) && return :($i + 1)
69+
@capture(x, prevind(str_, i_)) && return :($i - 1)
70+
return x
71+
end
72+
nextinds (generic function with 1 method)
73+
74+
julia> nextinds(:(nextind(a, b)))
75+
:(b + 1)
76+
```
77+
78+
We can check this works on text, before running it on our file, using `textwalk`.
79+
80+
```julia
81+
julia> using MacroTools: textwalk
82+
83+
julia> textwalk(nextinds, "nextind(s, i)")
84+
"i + 1"
85+
```
86+
87+
And verify that it correctly handles operator precedence – so we won't get invalid
88+
syntax or unexpected changes to the meaning of our code.
89+
90+
```julia
91+
julia> textwalk(nextinds, "1:nextind(s, i)")
92+
"1:i + 1"
93+
94+
julia> textwalk(nextinds, "1*nextind(s, i)")
95+
"1*(i + 1)"
96+
```
97+
98+
Running this on Julia's base code yields [the following patch](https://github.com/MikeInnes/julia/commit/b3964317321150c4b9ae8d629f613ee1143b3629).

src/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function rmlines(x::Expr)
6363
# Do not strip the first argument to a macrocall, which is
6464
# required.
6565
if x.head == :macrocall && length(x.args) >= 2
66-
Expr(x.head, x.args[1:2]..., filter(x->!isline(x), x.args[3:end])...)
66+
Expr(x.head, x.args[1], nothing, filter(x->!isline(x), x.args[3:end])...)
6767
else
6868
Expr(x.head, filter(x->!isline(x), x.args)...)
6969
end

0 commit comments

Comments
 (0)