Skip to content

Commit

Permalink
WIP: Switch to using ExprTools
Browse files Browse the repository at this point in the history
Docs need revision yet
  • Loading branch information
omus committed Jan 31, 2020
1 parent 1d9e8f6 commit 2b7648c
Show file tree
Hide file tree
Showing 4 changed files with 7 additions and 139 deletions.
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ version = "0.5.3"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
DataStructures = "0.17"
ExprTools = "0.1"
julia = "1"

[extras]
Expand Down
4 changes: 3 additions & 1 deletion src/MacroTools.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module MacroTools

using DataStructures, Markdown, Random
export @match, @capture
using ExprTools: splitdef, combinedef

export @match, @capture, splitdef

include("match/match.jl")
include("match/types.jl")
Expand Down
85 changes: 1 addition & 84 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export @esc, isexpr, isline, iscall, rmlines, unblock, block, inexpr, namify, isdef,
longdef, shortdef, @expand, makeif, prettify, splitdef, splitarg
longdef, shortdef, @expand, makeif, prettify, splitarg

"""
assoc!(d, k, v)
Expand Down Expand Up @@ -261,89 +261,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)
@assert(@capture(fcall_nowhere, ((func_(args__; kwargs__)) |
(func_(args__; kwargs__)::rtype_) |
(func_(args__)) |
(func_(args__)::rtype_))),
error_msg)
@assert(@capture(func, (fname_{params__} | fname_)), error_msg)
di = Dict(:name=>fname, :args=>args,
:kwargs=>(kwargs===nothing ? [] : kwargs), :body=>body)
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])
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
end

"""
combinearg(arg_name, arg_type, is_splat, default)
Expand Down
55 changes: 1 addition & 54 deletions test/split.jl
Original file line number Diff line number Diff line change
@@ -1,61 +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)
end

@testset "combinestructdef, splitstructdef" begin
ex = :(struct S end)
@test ex |> splitstructdef |> combinestructdef |> Base.remove_linenums! ==
Expand Down

0 comments on commit 2b7648c

Please sign in to comment.