Created
April 23, 2017 00:46
-
-
Save fcard/62dbdfd09432461af8ac0b86083c71e7 to your computer and use it in GitHub Desktop.
Transform expressions into functions where free variables are turned into keyword arguments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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