# using Pkg
# Pkg.add("https://github.com/genkuroki/InteractiveUtilsPlus.jl")
#
using InteractiveUtilsPlus
using Base64
showimg(mime, fn; tag="img") = open(fn) do f
base64 = base64encode(f)
display("text/html", """<$tag src="data:$mime;base64,$base64" />""")
end
showimg (generic function with 1 method)
module A
macro insert_before_after(funcdef, before, after)
funcdef.args[2] = Expr(:block, before, funcdef.args[2], after)
esc(funcdef)
end
end
Main.A
# マクロの展開のされ方
expr = @macroexpand A.@insert_before_after function printadd(a, b)
println(a, " + ", b, " = ", a + b)
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
Base.remove_linenums!(expr)
:(function printadd(a, b) begin t_start = time() println("start at ", t_start) end begin println(a, " + ", b, " = ", a + b) end begin t_end = time() println("end at ", t_end) println("time elapsed: ", t_end - t_start) end end)
A.@insert_before_after function printadd(a, b)
println(a, " + ", b, " = ", a + b)
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
printadd (generic function with 1 method)
printadd(3, 7)
start at 1.602232519071e9 3 + 7 = 10 end at 1.602232519275e9 time elapsed: 0.20399999618530273
printadd(big"2"^200, big"2"^300)
start at 1.602232519353e9 1606938044258990275541962092341162602522202993782792835301376 + 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 = 2037035976334486086268445688410985099095727383941478212728481611956903502757119499018698752 end at 1.602232519392e9 time elapsed: 0.03900003433227539
@show_tree function printadd(a::T, b::T; c::T=1, d::T=2) where T<:Integer
println(a + b + c + d)
end
:function ├─ :where │ ├─ :call │ │ ├─ :printadd │ │ ├─ :parameters │ │ │ ├─ :kw │ │ │ │ ├─ :(::) │ │ │ │ │ ├─ :c │ │ │ │ │ └─ :T │ │ │ │ └─ 1 │ │ │ └─ :kw │ │ │ ├─ :(::) │ │ │ │ ├─ :d │ │ │ │ └─ :T │ │ │ └─ 2 │ │ ├─ :(::) │ │ │ ├─ :a │ │ │ └─ :T │ │ └─ :(::) │ │ ├─ :b │ │ └─ :T │ └─ :<: │ ├─ :T │ └─ :Integer └─ :block └─ :call ├─ :println └─ :call ├─ :+ ├─ :a ├─ :b ├─ :c └─ :d
Meta.@dump function printadd(a::T, b::T; c::T=1, d::T=2) where T<:Integer
println(a + b + c + d)
end
Expr head: Symbol function args: Array{Any}((2,)) 1: Expr head: Symbol where args: Array{Any}((2,)) 1: Expr head: Symbol call args: Array{Any}((4,)) 1: Symbol printadd 2: Expr head: Symbol parameters args: Array{Any}((2,)) 1: Expr 2: Expr 3: Expr head: Symbol :: args: Array{Any}((2,)) 1: Symbol a 2: Symbol T 4: Expr head: Symbol :: args: Array{Any}((2,)) 1: Symbol b 2: Symbol T 2: Expr head: Symbol <: args: Array{Any}((2,)) 1: Symbol T 2: Symbol Integer 2: Expr head: Symbol block args: Array{Any}((3,)) 1: LineNumberNode line: Int64 1 file: Symbol In[9] 2: LineNumberNode line: Int64 2 file: Symbol In[9] 3: Expr head: Symbol call args: Array{Any}((2,)) 1: Symbol println 2: Expr head: Symbol call args: Array{Any}((5,)) 1: Symbol + 2: Symbol a 3: Symbol b 4: Symbol c 5: Symbol d
上の A.@insert_before_after
には函数の返り値が変わってしまうという欠陥がある. それを改善しよう.
module B
macro insert_before_after(funcdef, before, after)
val = gensym()
body = Expr(:(=), val, funcdef.args[2])
funcdef.args[2] = Expr(:block, before, body, after, val)
esc(funcdef)
end
end
Main.B
# マクロの展開のされ方
expr = @macroexpand B.@insert_before_after function printadd(a, b)
println(a, " + ", b, " = ", a + b)
a + b
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
Base.remove_linenums!(expr)
:(function printadd(a, b) begin t_start = time() println("start at ", t_start) end var"##249" = begin println(a, " + ", b, " = ", a + b) a + b end begin t_end = time() println("end at ", t_end) println("time elapsed: ", t_end - t_start) end var"##249" end)
print_tree(expr, 10)
:function ├─ :call │ ├─ :printadd │ ├─ :a │ └─ :b └─ :block ├─ :block │ ├─ :(=) │ │ ├─ :t_start │ │ └─ :call │ │ └─ :time │ └─ :call │ ├─ :println │ ├─ "start at " │ └─ :t_start ├─ :(=) │ ├─ Symbol("##249") │ └─ :block │ ├─ :call │ │ ├─ :println │ │ ├─ :a │ │ ├─ " + " │ │ ├─ :b │ │ ├─ " = " │ │ └─ :call │ │ ├─ :+ │ │ ├─ :a │ │ └─ :b │ └─ :call │ ├─ :+ │ ├─ :a │ └─ :b ├─ :block │ ├─ :(=) │ │ ├─ :t_end │ │ └─ :call │ │ └─ :time │ ├─ :call │ │ ├─ :println │ │ ├─ "end at " │ │ └─ :t_end │ └─ :call │ ├─ :println │ ├─ "time elapsed: " │ └─ :call │ ├─ :- │ ├─ :t_end │ └─ :t_start └─ Symbol("##249")
B.@insert_before_after function printadd(a, b)
println(a, " + ", b, " = ", a + b)
a + b
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
printadd (generic function with 1 method)
printadd(123, 456)
start at 1.602232521409e9 123 + 456 = 579 end at 1.60223252141e9 time elapsed: 0.0010001659393310547
579
cf. @time マクロ
B.@insert_before_after function foo(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
foo (generic function with 1 method)
foo(123)
start at 1.602232521868e9 123 + 456 = 579 end at 1.602232521869e9 time elapsed: 0.0009999275207519531
579
expr = @macroexpand B.@insert_before_after function foo(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end begin
t_start = time()
println("start at ", t_start)
end begin
t_end = time()
println("end at ", t_end)
println("time elapsed: ", t_end - t_start)
end
Base.remove_linenums!(expr)
:(function foo(a::T; b = 456) where T <: Real begin t_start = time() println("start at ", t_start) end var"##252" = begin println(a, " + ", b, " = ", a + b) a + b end begin t_end = time() println("end at ", t_end) println("time elapsed: ", t_end - t_start) end var"##252" end)
print_tree(expr, 10)
:function ├─ :where │ ├─ :call │ │ ├─ :foo │ │ ├─ :parameters │ │ │ └─ :kw │ │ │ ├─ :b │ │ │ └─ 456 │ │ └─ :(::) │ │ ├─ :a │ │ └─ :T │ └─ :<: │ ├─ :T │ └─ :Real └─ :block ├─ :block │ ├─ :(=) │ │ ├─ :t_start │ │ └─ :call │ │ └─ :time │ └─ :call │ ├─ :println │ ├─ "start at " │ └─ :t_start ├─ :(=) │ ├─ Symbol("##252") │ └─ :block │ ├─ :call │ │ ├─ :println │ │ ├─ :a │ │ ├─ " + " │ │ ├─ :b │ │ ├─ " = " │ │ └─ :call │ │ ├─ :+ │ │ ├─ :a │ │ └─ :b │ └─ :call │ ├─ :+ │ ├─ :a │ └─ :b ├─ :block │ ├─ :(=) │ │ ├─ :t_end │ │ └─ :call │ │ └─ :time │ ├─ :call │ │ ├─ :println │ │ ├─ "end at " │ │ └─ :t_end │ └─ :call │ ├─ :println │ ├─ "time elapsed: " │ └─ :call │ ├─ :- │ ├─ :t_end │ └─ :t_start └─ Symbol("##252")
# original
expr_original = :(function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end)
Base.remove_linenums!(expr_original)
:(function bar(a::T; b = 456) where T <: Real println(a, " + ", b, " = ", a + b) a + b end)
expr_modified = @macroexpand B.@insert_before_after function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end println("bar: write start log") println("bar: write end log")
Base.remove_linenums!(expr_modified)
:(function bar(a::T; b = 456) where T <: Real println("bar: write start log") var"##253" = begin println(a, " + ", b, " = ", a + b) a + b end println("bar: write end log") var"##253" end)
print_tree(expr_original, 10)
:function ├─ :where │ ├─ :call │ │ ├─ :bar │ │ ├─ :parameters │ │ │ └─ :kw │ │ │ ├─ :b │ │ │ └─ 456 │ │ └─ :(::) │ │ ├─ :a │ │ └─ :T │ └─ :<: │ ├─ :T │ └─ :Real └─ :block ├─ :call │ ├─ :println │ ├─ :a │ ├─ " + " │ ├─ :b │ ├─ " = " │ └─ :call │ ├─ :+ │ ├─ :a │ └─ :b └─ :call ├─ :+ ├─ :a └─ :b
print_tree(expr_modified, 10)
:function ├─ :where │ ├─ :call │ │ ├─ :bar │ │ ├─ :parameters │ │ │ └─ :kw │ │ │ ├─ :b │ │ │ └─ 456 │ │ └─ :(::) │ │ ├─ :a │ │ └─ :T │ └─ :<: │ ├─ :T │ └─ :Real └─ :block ├─ :call │ ├─ :println │ └─ "bar: write start log" ├─ :(=) │ ├─ Symbol("##253") │ └─ :block │ ├─ :call │ │ ├─ :println │ │ ├─ :a │ │ ├─ " + " │ │ ├─ :b │ │ ├─ " = " │ │ └─ :call │ │ ├─ :+ │ │ ├─ :a │ │ └─ :b │ └─ :call │ ├─ :+ │ ├─ :a │ └─ :b ├─ :call │ ├─ :println │ └─ "bar: write end log" └─ Symbol("##253")
sleep(0.1)
showimg("image/png", "HowToModifyAST.png", tag="img width=90%")
function start_log(func_name)
println("$(func_name): write start log")
end
function end_log(func_name)
println("$(func_name): write end log")
end
macro withlog_simple(ex)
if Meta.isexpr(ex, :function)
s = string(ex.args[1].args[1])
quote
function $(esc(ex.args[1].args[1]))($(esc.(ex.args[1].args[2:end])...))
start_log($s)
$(esc(ex.args[2]))
end_log($s)
end
end
else
esc(ex)
end
end
@withlog_simple (macro with 1 method)
@withlog_simple function bar(a; b=456)
println(a, " + ", b, " = ", a + b)
a + b
end
bar(123; b=456)
bar: write start log 123 + 456 = 579 bar: write end log
@withlog_simple function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end
syntax: "T <: Real" is not a valid function argument name around In[24]:13 Stacktrace: [1] top-level scope @ In[26]:1 [2] eval @ .\boot.jl:360 [inlined] [3] include_string(mapexpr::IJulia.var"#37#38", mod::Module, code::String, filename::String) @ Base .\loading.jl:1051 [4] execute_code(code::String, filename::String) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\execute_request.jl:27 [5] execute_request(socket::ZMQ.Socket, msg::IJulia.Msg) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\execute_request.jl:86 [6] #invokelatest#2 @ .\essentials.jl:709 [inlined] [7] invokelatest @ .\essentials.jl:708 [inlined] [8] eventloop(socket::ZMQ.Socket) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\eventloop.jl:8 [9] (::IJulia.var"#15#18")() @ IJulia .\task.jl:395
expr = @macroexpand @withlog_simple function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end
Base.remove_linenums!(expr)
quote function (bar(a::T; b = 456))(T <: Real) Main.start_log("bar(a::T; b = 456)") begin println(a, " + ", b, " = ", a + b) a + b end Main.end_log("bar(a::T; b = 456)") end end
using MacroTools
macro withlog(ex)
fndef = splitdef(ex)
s = string(fndef[:name])
quote
function $(esc(fndef[:name]))($(esc.(fndef[:args])...))
start_log($s)
$(esc(fndef[:body]))
end_log($s)
end
end
# fndef[:name] = esc(fndef[:name])
# fndef[:args] = esc.(fndef[:args])
# bdy = quote
# start_log($s)
# $(esc(fndef[:body]))
# end_log($s)
# end
# fndef[:body] = bdy
# MacroTools.combinedef(fndef)
end
@withlog (macro with 1 method)
@withlog function bar(a; b=456)
println(a, " + ", b, " = ", a + b)
a + b
end
bar(123; b=456)
bar: write start log 123 + 456 = 579 bar: write end log
@withlog function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end
UndefVarError: T not defined Stacktrace: [1] top-level scope @ In[28]:7 [2] eval @ .\boot.jl:360 [inlined] [3] include_string(mapexpr::IJulia.var"#37#38", mod::Module, code::String, filename::String) @ Base .\loading.jl:1051 [4] execute_code(code::String, filename::String) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\execute_request.jl:27 [5] execute_request(socket::ZMQ.Socket, msg::IJulia.Msg) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\execute_request.jl:86 [6] #invokelatest#2 @ .\essentials.jl:709 [inlined] [7] invokelatest @ .\essentials.jl:708 [inlined] [8] eventloop(socket::ZMQ.Socket) @ IJulia ~\.julia\packages\IJulia\rWZ9e\src\eventloop.jl:8 [9] (::IJulia.var"#15#18")() @ IJulia .\task.jl:395
expr = @macroexpand @withlog function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end
Base.remove_linenums!(expr)
quote function bar(a::T) Main.start_log("bar") begin println(a, " + ", b, " = ", a + b) a + b end Main.end_log("bar") end end
B.@insert_before_after function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end println("bar: write start log") println("bar: write end log")
bar(123)
bar: write start log 123 + 456 = 579 bar: write end log
579
splitdef(:(function bar(a; b=456)
println(a, " + ", b, " = ", a + b)
a + b
end))
Dict{Symbol, Any} with 5 entries: :name => :bar :args => Any[:a] :kwargs => Any[:($(Expr(:kw, :b, 456)))] :body => quote… :whereparams => ()
splitdef(:(function bar(a::T; b=456) where T<:Real
println(a, " + ", b, " = ", a + b)
a + b
end))
Dict{Symbol, Any} with 5 entries: :name => :bar :args => Any[:(a::T)] :kwargs => Any[:($(Expr(:kw, :b, 456)))] :body => quote… :whereparams => (:(T <: Real),)