Skip to content

WIP: Depend on ExprTools.jl #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.jl.cov
*.jl.*.cov
*.jl.mem
/Manifest.toml

.DS_Store

Expand Down
15 changes: 0 additions & 15 deletions Manifest.toml

This file was deleted.

2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 2 additions & 0 deletions src/MacroTools.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module MacroTools

using Markdown, Random
using ExprTools: splitdef, combinedef

export @match, @capture

include("match/match.jl")
Expand Down
121 changes: 0 additions & 121 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
69 changes: 1 addition & 68 deletions test/split.jl
Original file line number Diff line number Diff line change
@@ -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! ==
Expand Down