Skip to content

Instantly share code, notes, and snippets.

@fcard
Created April 23, 2017 00:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fcard/62dbdfd09432461af8ac0b86083c71e7 to your computer and use it in GitHub Desktop.
Save fcard/62dbdfd09432461af8ac0b86083c71e7 to your computer and use it in GitHub Desktop.
Transform expressions into functions where free variables are turned into keyword arguments
module ExprKw
export Missing, @kw, @arg, @noarg
type Missing{Var,T} <: Exception ex::T end
Base.showerror{Var}(io::IO, err::Missing{Var}) = println(io, "Missing variable $Var in expression ($(err.ex))")
@generated function check_for_missing(args...)
for i in eachindex(args)
args[i] <: Missing && return :(throw(args[$i]))
end
end
macro kw(ex)
func = gensym()
args = obtain_arguments(ex)
quote
function $func(;$((Expr(:kw, arg, Missing{arg,typeof(ex)}(ex)) for arg in args)...))
check_for_missing($(args...))
$(esc(ex))
end
$func
end
end
macro noarg(x)
esc(x)
end
macro arg(x)
esc(x)
end
function ignore_quoted(f, ex::Expr, n=1)
if ex.head == :quote
ignore_quoted(f, arg, n+1)
elseif ex.head == :$
if n == 1
f(ex.args[1])
else
ignore_quoted(f, arg, n-1)
end
else
foreach(arg->ignore_quoted(f, arg, n), ex.args)
end
end
ignore_quoted(ex) = nothing
immutable Shadower
shadowed::Set{Symbol}
end
immutable ArgumentObtainer
result::Set{Symbol}
shadow::Shadower
end
ArgumentObtainer() = ArgumentObtainer(Set(), Shadower(Set()))
(s::Shadower)(ex::Symbol) = push!(s.shadowed, ex)
(s::Shadower)(ex::Expr) = ex.head == :tuple && foreach(s, args)
new_scope(a::ArgumentObtainer) = ArgumentObtainer(a.result, Shadower(copy(a.shadow.shadowed)))
(a::ArgumentObtainer)(ex) = nothing
(a::ArgumentObtainer)(ex::Symbol) = ex in a.shadow.shadowed || push!(a.result, ex)
function (obtain_args::ArgumentObtainer)(ex::Expr)
if ex.head == :call
if is_macrocall(ex.args[1], "@arg")
obtain_args(ex.args[1].args[2])
end
foreach(obtain_args, ex.args[2:end])
elseif ex.head in (:(=), :kw)
obtain_args(ex.args[2])
obtain_args.shadow(ex.args[1])
elseif ex.head in (:local, :global) && isa(ex.args[1], Symbol)
foreach(obtain_args.shadow, ex.args)
elseif ex.head == :.
obtain_args(ex.args[1])
elseif ex.head == :quote
ignore_quoted(obtain_args, ex.args[1])
elseif ex.head == :line || is_macrocall(ex, "@noarg")
nothing
elseif ex.head == :let
obtain_args = new_scope(obtain_args)
for arg in ex.args[2:end]
isa(arg, Symbol) ? obtain_args.shadow(arg) : obtain_args(arg)
end
obtain_args(ex.args[1])
elseif ex.head in (:function, :(->))
args = function_args(ex.args[1])
body = ex.args[2]
obtain_args = new_scope(obtain_args)
for arg in args
!isa(arg, Expr) ? obtain_args.shadow(arg) : (obtain_args.shadow(arg.args[1]); obtain_args(arg.args[2]))
end
obtain_args(body)
elseif ex.head == :macrocall
obtain_args(macroexpand(ex))
elseif ex.head == :block
foreach(obtain_args, ex.args)
else
foreach(new_scope(obtain_args), ex.args)
end
end
function obtain_arguments(ex)
obtainer = ArgumentObtainer()
obtainer(ex)
obtainer.result
end
function_args(ex::Symbol) = [ex]
function_args(ex::Expr) = ex.head == :call ? ex.args[2:end] : ex.args
is_macrocall(ex, name) = false
is_macrocall(ex::Expr, name) = ex.head == :macrocall && String(ex.args[1]) == name
end
module ExprKwTests
using ExprKw
using Base.Test
c1 = @kw a^b
c2 = @kw (a+b) * (c-d)
c3 = @kw let x=1; a+x end
c4 = @kw let x=1; a+x end + x
c5 = @kw ((x,y)->x+y)(a,y)
c6 = @kw a::@noarg(Int)
c7 = @kw c1(;a...)
c8 = @kw @arg(f)(a,b)
c9 = @kw @static(false ? x : y)
@test c1(a=3, b=2) == 9
@test c2(a=1, b=2, c=3, d=4) == -3
@test c3(a=1) == 2
@test c4(a=1,x=2) == 4
@test c5(a=1,y=2) == 3
@test c6(a=10) == 10
@test c7(a=((:a=>3),(:b=>2))) == 9
@test c8(f=+, a=1, b=1) == 2
@test c9(y=1) == 1
@test_throws Missing{:b} c1(a=3)
@test_throws Missing{:a} c6()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment