Skip to content

Commit

Permalink
Merge pull request JuliaLang#17004 from c42f/popmeta-just-try-harder
Browse files Browse the repository at this point in the history
Make popmeta! search further than the first meta expression
  • Loading branch information
JeffBezanson authored Jun 18, 2016
2 parents c2a54e6 + 9efe8e3 commit 9840b23
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 36 deletions.
38 changes: 22 additions & 16 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,31 @@ function popmeta!(body::Expr, sym::Symbol)
end
popmeta!(arg, sym) = (false, [])
function popmeta!(body::Array{Any,1}, sym::Symbol)
idx, args = findmeta_block(body)
idx, blockargs = findmeta_block(body, args -> findmetaarg(args,sym)!=0)
if idx == 0
return false, []
end
metaex = args[idx]
metaargs = metaex.args
metaargs = blockargs[idx].args
i = findmetaarg(blockargs[idx].args, sym)
if i == 0
return false, []
end
ret = isa(metaargs[i], Expr) ? (metaargs[i]::Expr).args : []
deleteat!(metaargs, i)
isempty(metaargs) && deleteat!(blockargs, idx)
true, ret
end

# Find index of `sym` in a meta expression argument list, or 0.
function findmetaarg(metaargs, sym)
for i = 1:length(metaargs)
if isa(metaargs[i], Symbol) && (metaargs[i]::Symbol) == sym
deleteat!(metaargs, i)
isempty(metaargs) && deleteat!(args, idx)
return true, []
elseif isa(metaargs[i], Expr) && (metaargs[i]::Expr).head == sym
ret = (metaargs[i]::Expr).args
deleteat!(metaargs, i)
isempty(metaargs) && deleteat!(args, idx)
return true, ret
arg = metaargs[i]
if (isa(arg, Symbol) && (arg::Symbol) == sym) ||
(isa(arg, Expr) && (arg::Expr).head == sym)
return i
end
end
false, []
return 0
end

function findmeta(ex::Expr)
Expand All @@ -165,14 +171,14 @@ end

findmeta(ex::Array{Any,1}) = findmeta_block(ex)

function findmeta_block(exargs)
function findmeta_block(exargs, argsmatch=args->true)
for i = 1:length(exargs)
a = exargs[i]
if isa(a, Expr)
if (a::Expr).head == :meta
if (a::Expr).head == :meta && argsmatch((a::Expr).args)
return i, exargs
elseif (a::Expr).head == :block
idx, exa = findmeta_block(a.args)
idx, exa = findmeta_block(a.args, argsmatch)
if idx != 0
return idx, exa
end
Expand Down
8 changes: 4 additions & 4 deletions doc/devdocs/meta.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ to specify additional information.
To use the metadata, you have to parse these ``:meta`` expressions.
If your implementation can be performed within Julia, ``Base.popmeta!`` is
very handy: ``Base.popmeta!(body, :symbol)`` will scan a function *body*
expression (one without the function signature) for a ``:meta``
expression, extract any arguments, and return a tuple ``(found::Bool,
args::Array{Any})``. If the metadata did not have any arguments, or
``:symbol`` was not found, the ``args`` array will be empty.
expression (one without the function signature) for the first ``:meta``
expression containing ``:symbol``, extract any arguments, and return a tuple
``(found::Bool, args::Array{Any})``. If the metadata did not have any
arguments, or ``:symbol`` was not found, the ``args`` array will be empty.

Not yet provided is a convenient infrastructure for parsing ``:meta``
expressions from C++.
42 changes: 26 additions & 16 deletions test/meta.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,38 @@ body.args = Base.uncompressed_ast(ast)
@test popmeta!(body, :test) == (true, [42])
@test popmeta!(body, :nonexistent) == (false, [])

metaex = Expr(:meta, :foo)
# Simple popmeta!() tests
ex1 = quote
$metaex
$(Expr(:meta, :foo))
x*x+1
end
metaex = Expr(:meta, :foo)
ex2 = quote
y = x
$metaex
y*y+1
end
metaex = Expr(:block, Expr(:meta, :foo))
ex3 = quote
@test popmeta!(ex1, :foo)[1]
@test !popmeta!(ex1, :foo)[1]
@test !popmeta!(ex1, :bar)[1]
@test !(popmeta!(:(x*x+1), :foo)[1])

# Find and pop meta information from general ast locations
multi_meta = quote
$(Expr(:meta, :foo1))
y = x
$metaex
$(Expr(:meta, :foo2, :foo3))
begin
$(Expr(:meta, :foo4, Expr(:foo5, 1, 2)))
end
x*x+1
end

@test popmeta!(ex1, :foo)[1]
@test popmeta!(ex2, :foo)[1]
@test popmeta!(ex3, :foo)[1]
@test !(popmeta!(:(x*x+1), :foo)[1])
@test popmeta!(deepcopy(multi_meta), :foo1) == (true, [])
@test popmeta!(deepcopy(multi_meta), :foo2) == (true, [])
@test popmeta!(deepcopy(multi_meta), :foo3) == (true, [])
@test popmeta!(deepcopy(multi_meta), :foo4) == (true, [])
@test popmeta!(deepcopy(multi_meta), :foo5) == (true, [1,2])
@test popmeta!(deepcopy(multi_meta), :bar) == (false, [])

# Test that popmeta!() removes meta blocks entirely when they become empty.
for m in [:foo1, :foo2, :foo3, :foo4, :foo5]
@test popmeta!(multi_meta, m)[1]
end
@test Base.findmeta(multi_meta.args)[1] == 0

end

Expand Down

0 comments on commit 9840b23

Please sign in to comment.