diff --git a/.gitignore b/.gitignore index b9f53d7..f463bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.jl.cov *.jl.*.cov *.jl.mem +/Manifest.toml .DS_Store diff --git a/Manifest.toml b/Manifest.toml deleted file mode 100644 index dffc979..0000000 --- a/Manifest.toml +++ /dev/null @@ -1,15 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -[[Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[Random]] -deps = ["Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" diff --git a/Project.toml b/Project.toml index b8db1c3..0db26ed 100644 --- a/Project.toml +++ b/Project.toml @@ -3,10 +3,12 @@ uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.5" [deps] +ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] +ExprTools = "0.1" julia = "1" [extras] diff --git a/src/MacroTools.jl b/src/MacroTools.jl index 4bd41dd..5a214fa 100644 --- a/src/MacroTools.jl +++ b/src/MacroTools.jl @@ -1,6 +1,8 @@ module MacroTools using Markdown, Random +using ExprTools: splitdef, combinedef + export @match, @capture include("match/match.jl") diff --git a/src/utils.jl b/src/utils.jl index 7cb4a88..dc44a79 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -259,127 +259,6 @@ function gatherwheres(ex) end end -""" splitdef(fdef) - -Match any function definition - -```julia -function name{params}(args; kwargs)::rtype where {whereparams} - body -end -``` - -and return `Dict(:name=>..., :args=>..., etc.)`. The definition can be rebuilt by -calling `MacroTools.combinedef(dict)`, or explicitly with - -``` -rtype = get(dict, :rtype, :Any) -all_params = [get(dict, :params, [])..., get(dict, :whereparams, [])...] -:(function \$(dict[:name]){\$(all_params...)}(\$(dict[:args]...); - \$(dict[:kwargs]...))::\$rtype - \$(dict[:body]) - end) -``` -""" -function splitdef(fdef) - error_msg = "Not a function definition: $(repr(fdef))" - @assert(@capture(longdef1(fdef), - function (fcall_ | fcall_) body_ end), - "Not a function definition: $fdef") - fcall_nowhere, whereparams = gatherwheres(fcall) - func = args = kwargs = rtype = nothing - if @capture(fcall_nowhere, ((func_(args__; kwargs__)) | - (func_(args__; kwargs__)::rtype_) | - (func_(args__)) | - (func_(args__)::rtype_))) - elseif isexpr(fcall_nowhere, :tuple) - if length(fcall_nowhere.args) > 1 && isexpr(fcall_nowhere.args[1], :parameters) - args = fcall_nowhere.args[2:end] - kwargs = fcall_nowhere.args[1].args - else - args = fcall_nowhere.args - end - elseif isexpr(fcall_nowhere, :(::)) - args = Any[fcall_nowhere] - else - throw(ArgumentError(error_msg)) - end - if func !== nothing - @assert(@capture(func, (fname_{params__} | fname_)), error_msg) - di = Dict(:name=>fname, :args=>args, - :kwargs=>(kwargs===nothing ? [] : kwargs), :body=>body) - else - params = nothing - di = Dict(:args=>args, :kwargs=>(kwargs===nothing ? [] : kwargs), :body=>body) - end - if rtype !== nothing; di[:rtype] = rtype end - if whereparams !== nothing; di[:whereparams] = whereparams end - if params !== nothing; di[:params] = params end - di -end - -""" - combinedef(dict::Dict) - -`combinedef` is the inverse of `splitdef`. It takes a splitdef-like Dict -and returns a function definition. """ -function combinedef(dict::Dict) - rtype = get(dict, :rtype, nothing) - params = get(dict, :params, []) - wparams = get(dict, :whereparams, []) - body = block(dict[:body]) - if haskey(dict, :name) - name = dict[:name] - name_param = isempty(params) ? name : :($name{$(params...)}) - # We need the `if` to handle parametric inner/outer constructors like - # SomeType{X}(x::X) where X = SomeType{X}(x, x+2) - if isempty(wparams) - if rtype==nothing - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...)) - $(body.args...) - end) - else - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...))::$rtype - $(body.args...) - end) - end - else - if rtype==nothing - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...)) where {$(wparams...)} - $(body.args...) - end) - else - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...))::$rtype where {$(wparams...)} - $(body.args...) - end) - end - end - else - if isempty(dict[:kwargs]) - arg = :($(dict[:args]...),) - else - arg = Expr(:tuple, Expr(:parameters, dict[:kwargs]...), dict[:args]...) - end - if isempty(wparams) - if rtype==nothing - @q($arg -> $body) - else - @q(($arg::$rtype) -> $body) - end - else - if rtype==nothing - @q(($arg where {$(wparams...)}) -> $body) - else - @q(($arg::$rtype where {$(wparams...)}) -> $body) - end - end - end -end - """ combinearg(arg_name, arg_type, is_splat, default) diff --git a/test/split.jl b/test/split.jl index 940a68c..2496919 100644 --- a/test/split.jl +++ b/test/split.jl @@ -1,75 +1,8 @@ using MacroTools: splitstructdef, combinestructdef -macro nothing_macro() -end +macro nothing_macro() end @test @expand(@nothing_macro) === nothing -macro splitcombine(fundef) # should be a no-op - dict = splitdef(fundef) - esc(MacroTools.combinedef(dict)) -end - -# Macros for testing that splitcombine doesn't break -# macrocalls in bodies -macro zeroarg() - :(1) -end -macro onearg(x) - :(1+$(esc(x))) -end - -let - # Ideally we'd compare the result against :(function f(x)::Int 10 end), - # but it fails because of :line and :block differences - @test longdef(:(f(x)::Int = 10)).head == :function - @test longdef(:(f(x::T) where U where T = 2)).head == :function - @test shortdef(:(function f(x)::Int 10 end)).head != :function - @test map(splitarg, (:(f(a=2, x::Int=nothing, y, args...))).args[2:end]) == - [(:a, :Any, false, 2), (:x, :Int, false, :nothing), - (:y, :Any, false, nothing), (:args, :Any, true, nothing)] - @test splitarg(:(::Int)) == (nothing, :Int, false, nothing) - - @splitcombine foo(x) = x+2 - @test foo(10) == 12 - @splitcombine add(a, b=2; c=3, d=4)::Float64 = a+b+c+d - @test add(1; d=10) === 16.0 - @splitcombine fparam(a::T) where {T} = T - @test fparam([]) == Vector{Any} - struct Orange end - @splitcombine (::Orange)(x) = x+2 - @test Orange()(10) == 12 - @splitcombine fwhere(a::T) where T = T - @test fwhere(10) == Int - @splitcombine manywhere(x::T, y::Vector{U}) where T <: U where U = (T, U) - @test manywhere(1, Number[2.0]) == (Int, Number) - @splitcombine fmacro0() = @zeroarg - @test fmacro0() == 1 - @splitcombine fmacro1() = @onearg 1 - @test fmacro1() == 2 - - struct Foo{A, B} - a::A - b::B - end - # Parametric outer constructor - @splitcombine Foo{A}(a::A) where A = Foo{A, A}(a,a) - @test Foo{Int}(2) == Foo{Int, Int}(2, 2) - - @test (@splitcombine x -> x + 2)(10) === 12 - @test (@splitcombine (a, b=2; c=3, d=4) -> a+b+c+d)(1; d=10) === 16 - @test (@splitcombine ((a, b)::Tuple{Int,Int} -> a + b))((1, 2)) == 3 - @test (@splitcombine ((a::T) where {T}) -> T)([]) === Vector{Any} - @test (@splitcombine ((x::T, y::Vector{U}) where T <: U where U) -> (T, U))(1, Number[2.0]) == - (Int, Number) - @test (@splitcombine () -> @zeroarg)() == 1 - @test (@splitcombine () -> @onearg 1)() == 2 - @test (@splitcombine function (x) x + 2 end)(10) === 12 - @test (@splitcombine function (a::T) where {T} T end)([]) === Vector{Any} - @test (@splitcombine function (x::T, y::Vector{U}) where T <: U where U - (T, U) - end)(1, Number[2.0]) == (Int, Number) -end - @testset "combinestructdef, splitstructdef" begin ex = :(struct S end) @test ex |> splitstructdef |> combinestructdef |> Base.remove_linenums! ==