Skip to content

Commit 794d3a9

Browse files
committed
fix: scoping
1 parent 3f7d845 commit 794d3a9

File tree

6 files changed

+271
-39
lines changed

6 files changed

+271
-39
lines changed

docs/src/manual.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ DocTestSetup = quote
373373
@resumable function arrays_of_tuples()
374374
for u in [[(1,2),(3,4)], [(5,6),(7,8)]]
375375
for i in 1:2
376+
local val
376377
let i=i
377378
val = [a[i] for a in u]
378379
end
@@ -413,4 +414,4 @@ DocTestSetup = nothing
413414
- In a `try` block only top level `@yield` statements are allowed.
414415
- In a `catch` or a `finally` block a `@yield` statement is not allowed.
415416
- An anonymous function can not contain a `@yield` statement.
416-
- If a `FiniteStateMachineIterator` object is used in more than one `for` loop, only the `state` variable is reinitialised. A `@resumable function` that alters its arguments will use the modified values as initial parameters.
417+
- If a `FiniteStateMachineIterator` object is used in more than one `for` loop, only the `state` variable is reinitialised. A `@resumable function` that alters its arguments will use the modified values as initial parameters.

src/macro.jl

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ macro resumable(expr::Expr)
3434

3535
# The function that executes a step of the finite state machine
3636
func_def = splitdef(expr)
37+
@debug func_def[:body]
3738
rtype = :rtype in keys(func_def) ? func_def[:rtype] : Any
3839
args, kwargs, arg_dict = get_args(func_def)
3940
params = ((get_param_name(param) for param in func_def[:whereparams])...,)
@@ -43,7 +44,35 @@ macro resumable(expr::Expr)
4344
func_def[:body] = postwalk(transform_arg_yieldfrom, func_def[:body])
4445
func_def[:body] = postwalk(transform_yieldfrom, func_def[:body])
4546
func_def[:body] = postwalk(x->transform_for(x, ui8), func_def[:body])
47+
@debug func_def[:body]|>MacroTools.striplines
48+
#func_def[:body] = postwalk(x->transform_macro(x), func_def[:body])
49+
#@debug func_def[:body]|>MacroTools.striplines
50+
#func_def[:body] = postwalk(x->transform_macro_undo(x), func_def[:body])
51+
#@debug func_def[:body]|>MacroTools.striplines
52+
#func_def[:body] = postwalk(x->transform_let(x), func_def[:body])
53+
#@info func_def[:body]|>MacroTools.striplines
54+
#func_def[:body] = postwalk(x->transform_local(x), func_def[:body])
55+
#@info func_def[:body]|>MacroTools.striplines
56+
# Scoping fixes
57+
58+
# :name is :(fA::A) if it is an overloading call function (fA::A)(...)
59+
# ...
60+
if func_def[:name] isa Expr
61+
@assert func_def[:name].head == :(::)
62+
_name = func_def[:name].args[1]
63+
else
64+
_name = func_def[:name]
65+
end
66+
67+
scope = ScopeTracker(0, __module__, [Dict(i =>i for i in vcat(args, kwargs, [_name], params...))])
68+
#@info func_def[:body]|>MacroTools.striplines
69+
func_def[:body] = scoping(copy(func_def[:body]), scope)
70+
#@info func_def[:body]|>MacroTools.striplines
71+
func_def[:body] = postwalk(x->transform_remove_local(x), func_def[:body])
72+
#@info func_def[:body]|>MacroTools.striplines
73+
4674
inferfn, slots = get_slots(copy(func_def), arg_dict, __module__)
75+
@debug slots
4776

4877
# check if the resumable function is a callable struct instance (a functional) that is referencing itself
4978
isfunctional = @capture(func_def[:name], functional_::T_) && inexpr(func_def[:body], functional)
@@ -74,7 +103,6 @@ macro resumable(expr::Expr)
74103
fsmi._state = 0x00
75104
fsmi
76105
end
77-
78106
# the bare/fallback version of the constructor supplies default slot type parameters
79107
# we only need to define this if there there are actually slot defaults to be filled
80108
if !isempty(slot_T)
@@ -100,7 +128,6 @@ macro resumable(expr::Expr)
100128
end
101129
)
102130
@debug type_expr|>MacroTools.striplines
103-
104131
# The "original" function that now is simply a wrapper around the construction of the finite state machine
105132
call_def = copy(func_def)
106133
call_def[:rtype] = nothing
@@ -119,7 +146,7 @@ macro resumable(expr::Expr)
119146
end
120147
call_expr = combinedef(call_def) |> flatten
121148
@debug call_expr|>MacroTools.striplines
122-
149+
123150
# Finalizing the function stepping through the finite state machine
124151
if isempty(params)
125152
func_def[:name] = :((_fsmi::$type_name))
@@ -153,7 +180,6 @@ macro resumable(expr::Expr)
153180
call_expr = postwalk(x->x==:(ResumableFunctions.typed_fsmi) ? :(ResumableFunctions.typed_fsmi_fallback) : x, call_expr)
154181
end
155182
@debug func_expr|>MacroTools.striplines
156-
157183
# The final expression:
158184
# - the finite state machine struct
159185
# - the function stepping through the states

src/transforms.jl

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
function transform_remove_local(ex)
2+
ex isa Expr && ex.head === :local && return Expr(:block)
3+
return ex
4+
end
5+
6+
function transform_macro(ex)
7+
ex isa Expr || return ex
8+
ex.head !== :macrocall && return ex
9+
return Expr(:call, :__secret__, ex.args)
10+
end
11+
12+
function transform_macro_undo(ex)
13+
ex isa Expr || return ex
14+
(ex.head !== :call || ex.args[1] !== :__secret__) && return ex
15+
return Expr(:macrocall, ex.args[2]...)
16+
end
17+
118
"""
219
Function that replaces a variable
320
"""
@@ -70,22 +87,26 @@ Function that replaces a `for` loop by a corresponding `while` loop saving expli
7087
"""
7188
function transform_for(expr, ui8::BoxedUInt8)
7289
@capture(expr, for element_ in iterator_ body_ end) || return expr
90+
#@info element
91+
localelement = Expr(:local, element)
7392
ui8.n += one(UInt8)
7493
next = Symbol("_iteratornext_", ui8.n)
7594
state = Symbol("_iterstate_", ui8.n)
7695
iterator_value = Symbol("_iterator_", ui8.n)
7796
label = Symbol("_iteratorlabel_", ui8.n)
7897
body = postwalk(x->transform_continue(x, label), :(begin $(body) end))
79-
quote
98+
res = quote
8099
$iterator_value = $iterator
81-
@nosave $next = iterate($iterator_value)
100+
$next = iterate($iterator_value)
82101
while $next !== nothing
102+
$localelement
83103
($element, $state) = $next
84104
$body
85105
@label $label
86106
$next = iterate($iterator_value, $state)
87107
end
88108
end
109+
res
89110
end
90111

91112

@@ -102,7 +123,7 @@ Function that replaces a variable `x` in an expression by `_fsmi.x` where `x` is
102123
"""
103124
function transform_slots(expr, symbols)
104125
expr isa Expr || return expr
105-
expr.head === :let && return transform_slots_let(expr, symbols)
126+
#expr.head === :let && return transform_slots_let(expr, symbols)
106127
for i in 1:length(expr.args)
107128
expr.head === :kw && i === 1 && continue
108129
expr.head === Symbol("quote") && continue
@@ -111,27 +132,53 @@ function transform_slots(expr, symbols)
111132
expr
112133
end
113134

114-
"""
115-
Function that handles `let` block
116-
"""
117-
function transform_slots_let(expr::Expr, symbols)
118-
@capture(expr, let vars_; body_ end)
119-
locals = Set{Symbol}()
120-
(isa(vars, Expr) && vars.head==:(=)) || error("@resumable currently supports only single variable declarations in let blocks, i.e. only let blocks exactly of the form `let i=j; ...; end`. If you need multiple variables, please submit an issue on the issue tracker and consider contributing a patch.")
121-
sym = vars.args[1].args[2].value
122-
push!(locals, sym)
123-
vars.args[1] = sym
124-
body = postwalk(x->transform_let(x, locals), :(begin $(body) end))
125-
:(let $vars; $body end)
135+
#"""
136+
#Function that handles `let` block
137+
#"""
138+
#function transform_slots_let(expr::Expr, symbols)
139+
# @capture(expr, let vars_; body_ end)
140+
# locals = Set{Symbol}()
141+
# (isa(vars, Expr) && vars.head==:(=)) || error("@resumable currently supports only single variable declarations in let blocks, i.e. only let blocks exactly of the form `let i=j; ...; end`. If you need multiple variables, please submit an issue on the issue tracker and consider contributing a patch.")
142+
# sym = vars.args[1].args[2].value
143+
# push!(locals, sym)
144+
# vars.args[1] = sym
145+
# body = postwalk(x->transform_let(x, locals), :(begin $(body) end))
146+
# :(let $vars; $body end)
147+
#end
148+
149+
function transform_let(expr)
150+
expr isa Expr || return expr
151+
expr.head === :block && return expr
152+
#@info "inside transform let"
153+
@capture(expr, let arg_; body_; end) || return expr
154+
#@info "captured let"
155+
#arg |> dump
156+
#@info expr
157+
#@info arg
158+
#error("ASds")
159+
res = quote
160+
let
161+
local $arg
162+
$body
163+
end
164+
end
165+
#@info "emitting $res"
166+
res
167+
#expr.head === :. || return expr
168+
#expr = expr.args[2].value in symbols ? :($(expr.args[2].value)) : expr
126169
end
127170

128171
"""
129172
Function that replaces a variable `_fsmi.x` in an expression by `x` where `x` is a variable declared in a `let` block.
130173
"""
131-
function transform_let(expr, symbols::Set{Symbol})
174+
function transform_local(expr)
132175
expr isa Expr || return expr
133-
expr.head === :. || return expr
134-
expr = expr.args[2].value in symbols ? :($(expr.args[2].value)) : expr
176+
@capture(expr, local arg_ = ex_) || return expr
177+
res = quote
178+
local $arg
179+
$arg = $ex
180+
end
181+
res
135182
end
136183

137184
"""

src/utils.jl

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,153 @@ end
208208
function typed_fsmi_fallback(fsmi::Type{T}, fargs...)::T where T
209209
return T()
210210
end
211+
212+
mutable struct ScopeTracker
213+
i::Int
214+
mod::Module
215+
scope_stack::Vector
216+
end
217+
218+
function lookup!(s::Symbol, S::ScopeTracker; new = false)
219+
if isdefined(S.mod, s)
220+
return s
221+
end
222+
if !new
223+
for D in Iterators.reverse(S.scope_stack)
224+
if haskey(D, s)
225+
return D[s]
226+
end
227+
end
228+
end
229+
D = last(S.scope_stack)
230+
new = Symbol(s, Symbol("_$(S.i)"))
231+
S.i += 1
232+
D[s] = new
233+
return new
234+
end
235+
236+
scoping(e::LineNumberNode, scope) = e
237+
238+
scoping(e::Int, scope) = e
239+
240+
scoping(e::String, scope) = e
241+
scoping(e::typeof(ResumableFunctions.generate), scope) = e
242+
scoping(e::typeof(ResumableFunctions.IteratorReturn), scope) = e
243+
scoping(e::QuoteNode, scope) = e
244+
scoping(e::Bool, scope) = e
245+
scoping(e::Nothing, scope) = e
246+
247+
function scoping(s::Symbol, scope; new = false)
248+
#@info "scoping $s, $new"
249+
return lookup!(s, scope; new = new)
250+
end
251+
252+
function scoping(expr::Expr, scope)
253+
if expr.head === :macrocall
254+
for i in 2:length(expr.args)
255+
expr.args[i] = scoping(expr.args[i], scope)
256+
end
257+
return expr
258+
end
259+
new_stack = false
260+
if expr.head === :let
261+
# Replace
262+
# let i, k = 2, j = 1
263+
# [...]
264+
# end
265+
#
266+
# by
267+
#
268+
# let
269+
# local i_new
270+
# local k_new = 2
271+
# local j_new = 1
272+
# end
273+
#
274+
# Caveat:
275+
# let i = i, j = i
276+
#
277+
# must be
278+
# new_i = old_i
279+
# new_j = new_i
280+
#
281+
# :(
282+
283+
# defer adding a new scope after the right hand side have been renamed
284+
@capture(expr, let arg_; body_ end) || return expr
285+
@capture(arg, begin x__ end)
286+
replace_rhs = []
287+
for i in 1:length(x)
288+
y = x[i]
289+
fl = @capture(y, k_ = v_)
290+
if fl
291+
push!(replace_rhs, scoping(v, scope))
292+
else
293+
# there was no right side
294+
push!(replace_rhs, nothing)
295+
end
296+
end
297+
new_stack = true
298+
push!(scope.scope_stack, Dict())
299+
replace_lhs = []
300+
rep = []
301+
for i in 1:length(x)
302+
y = x[i]
303+
fl = @capture(y, k_ = v_)
304+
if fl
305+
push!(replace_lhs, scoping(k, scope, new = true))
306+
push!(rep, quote local $(replace_lhs[i]); $(replace_lhs[i]) = $(replace_rhs[i]) end)
307+
else
308+
@assert y isa Symbol
309+
push!(replace_lhs, scoping(y, scope, new = true))
310+
push!(rep, quote local $(replace_lhs[i]) end)
311+
end
312+
end
313+
rep = quote
314+
$(rep...)
315+
end
316+
rep = MacroTools.flatten(rep)
317+
expr.args[1] = Expr(:block)
318+
pushfirst!(expr.args[2].args, rep)
319+
320+
# Now continue recursively
321+
# but skip the local/dance, since we already replaced them
322+
for i in 2:length(expr.args[2].args)
323+
a = expr.args[2].args[i]
324+
expr.args[2].args[i] = scoping(a, scope)
325+
end
326+
pop!(scope.scope_stack)
327+
return expr
328+
end
329+
330+
if expr.head === :while || expr.head === :let
331+
push!(scope.scope_stack, Dict())
332+
new_stack = true
333+
end
334+
if expr.head === :local
335+
# this is my local dance
336+
# explain and rewrite using @capture
337+
if length(expr.args) == 1 && expr.args[1] isa Symbol
338+
expr.args[1] = scoping(expr.args[1], scope, new = true)
339+
elseif length(expr.args) == 1 && expr.args[1].head === :tuple
340+
for i in 1:length(expr.args[1].args)
341+
a = expr.args[1].args[i]
342+
expr.args[1].args[i] = scoping(a, scope, new = true)
343+
end
344+
else
345+
for i in 1:length(expr.args)
346+
a = expr.args[i]
347+
expr.args[i] = scoping(a, scope, new = true)
348+
end
349+
end
350+
else
351+
for i in 1:length(expr.args)
352+
a = expr.args[i]
353+
expr.args[i] = scoping(a, scope)
354+
end
355+
end
356+
if new_stack
357+
pop!(scope.scope_stack)
358+
end
359+
return expr
360+
end

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ end
1616

1717
macro doset(descr)
1818
quote
19+
@info "====================================="
20+
@info $descr
1921
if doset($descr)
2022
@safetestset $descr begin
2123
include("test_"*$descr*".jl")

0 commit comments

Comments
 (0)