-
Notifications
You must be signed in to change notification settings - Fork 28
/
lazymacro.jl
115 lines (97 loc) · 3.27 KB
/
lazymacro.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# Macros for lazy broadcasting,
# based on @dawbarton https://discourse.julialang.org/t/19641/20
# and @tkf https://github.com/JuliaLang/julia/issues/19198#issuecomment-457967851
# and @chethega https://github.com/JuliaLang/julia/pull/30939
using MacroTools
lazy(::Any) = throw(ArgumentError("function `lazy` exists only for its effect on broadcasting, see the macro @~"))
struct LazyCast{T}
value::T
end
Broadcast.broadcasted(::typeof(lazy), x) = LazyCast(x)
Broadcast.materialize(x::LazyCast) = x.value
is_call(ex) = isexpr(ex, :call) && !is_dotcall(ex)
""" Check if `ex` is a dot-call expression like `f.(x, y, z)` or `x .+ y .+ z`. """
is_dotcall(ex) = is_dotcall_nonop(ex) || is_dotcall_op(ex)
""" Check if `ex` is an expression like `f.(x, y, z)`. """
is_dotcall_nonop(ex) =
isexpr(ex, :.) && length(ex.args) == 2 && isexpr(ex.args[2], :tuple)
""" Check if `ex` is an expression like `x .+ y .+ z`. """
function is_dotcall_op(ex)
ex isa Expr && !isempty(ex.args) || return false
op = ex.args[1]
return op isa Symbol && Base.isoperator(op) && startswith(string(op), ".")
end
lazy_expr(x) = x
function lazy_expr(ex::Expr)
if is_dotcall(ex)
return bc_expr(ex)
elseif is_call(ex)
return app_expr(ex)
else
# TODO: Maybe better to support `a ? b : c` etc.? But how?
return ex
end
end
function bc_expr(ex::Expr)
@assert is_dotcall(ex)
return :($lazy.($(bc_expr_impl(ex))))
end
bc_expr_impl(x) = x
function bc_expr_impl(ex::Expr)
# walk down chain of dot calls
if is_dotcall_nonop(ex)
f = ex.args[1] # function name
args = ex.args[2].args
return Expr(ex.head, lazy_expr(f), Expr(:tuple, bc_expr_impl.(args)...))
elseif is_dotcall_op(ex)
f = ex.args[1] # function name (e.g., `.+`)
args = ex.args[2:end]
return Expr(ex.head, lazy_expr(f), bc_expr_impl.(args)...)
else
return lazy_expr(ex)
end
end
function app_expr(ex::Expr)
@assert is_call(ex)
return app_expr_impl(ex)
end
app_expr_impl(x) = x
function app_expr_impl(ex::Expr)
# walk down chain of calls and lazy-ify them
if is_call(ex)
return :($applied($(app_expr_impl.(ex.args)...)))
else
return lazy_expr(ex)
end
end
function checkex(ex)
if @capture(ex, (arg__,) = val_ )
if arg[2]==:dims
throw(ArgumentError("@~ is capturing keyword arguments, try with `; dims = $val` instead of a comma"))
else
throw(ArgumentError("@~ is probably capturing capturing keyword arguments, try with ; or brackets"))
end
end
if @capture(ex, (arg_,rest__) )
throw(ArgumentError("@~ is capturing more than one expression, try $name($arg) with brackets"))
end
ex
end
"""
@~ expr
Macro for creating a `Broadcasted` or `Applied` object. Regular calls
like `f(args...)` inside `expr` are replaced with `applied(f, args...)`.
Dotted-calls like `f(args...)` inside `expr` are replaced with
`broadcasted.(f, args...)`. Use `LazyArray(@~ expr)` if you need an
array-based interface.
```
julia> @~ A .+ B ./ 2
julia> @~ @. A + B / 2
julia> @~ A * B + C
```
"""
macro ~(ex)
checkex(ex)
# Expanding macro here to support, e.g., `@.`
esc(:($instantiate($(lazy_expr(macroexpand(__module__, ex))))))
end