Skip to content

Commit 01cb9a7

Browse files
authored
add a function to extract local variables from a frame (#54)
1 parent 8664e4d commit 01cb9a7

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

docs/src/dev_reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,6 @@ JuliaInterpreter.iswrappercall
7373
JuliaInterpreter.isdocexpr
7474
JuliaInterpreter.isglobalref
7575
JuliaInterpreter.statementnumber
76+
JuliaInterpreter.Variable
77+
JuliaInterpreter.locals
7678
```

src/JuliaInterpreter.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,51 @@ end
158158
JuliaStackFrame(frame::JuliaStackFrame, pc::JuliaProgramCounter; kwargs...) =
159159
JuliaStackFrame(frame.code, frame, pc; kwargs...)
160160

161+
"""
162+
`Variable` is a struct representing a variable with an asigned value.
163+
By calling the function `locals`[@ref] on a `JuliaStackFrame`[@ref] a
164+
`Vector` of `Variable`'s is returned.
165+
166+
Important fields:
167+
- `value::Any`: the value of the local variable
168+
- `name::Symbol`: the name of the variable as given in the source code
169+
- `isparam::Bool`: if the variable is a type parameter, for example `T` in `f(x::T) where {T} = x` .
170+
"""
171+
struct Variable
172+
value::Any
173+
name::Symbol
174+
isparam::Bool
175+
end
176+
Base.show(io::IO, var::Variable) = print(io, var.name, " = ", var.value)
177+
Base.isequal(var1::Variable, var2::Variable) =
178+
var1.value == var2.value && var1.name == var2.name && var1.isparam == var2.isparam
179+
180+
"""
181+
local_variables = locals(frame::JuliaStackFrame)::Vector{Variable}
182+
183+
Return the local variables as a vector of `Variable`[@ref].
184+
"""
185+
function locals(frame::JuliaStackFrame)
186+
vars = Variable[]
187+
syms = Set{Symbol}()
188+
for i = length(frame.locals):-1:1
189+
if !isa(frame.locals[i], Nothing)
190+
sym = frame.code.code.slotnames[i]
191+
sym in syms && continue
192+
push!(vars, Variable(something(frame.locals[i]), sym, false))
193+
push!(syms, sym)
194+
end
195+
end
196+
reverse!(vars)
197+
if frame.code.scope isa Method
198+
syms = sparam_syms(frame.code.scope)
199+
for i in 1:length(syms)
200+
push!(vars, Variable(frame.sparams[i], syms[i], true))
201+
end
202+
end
203+
return vars
204+
end
205+
161206
"""
162207
`framedict[method]` returns the `JuliaFrameCode` for `method`. For `@generated` methods,
163208
see [`genframedict`](@ref).

test/interpret.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,39 @@ end
274274
# Some expression can appear nontrivial but lower to nothing
275275
@test isa(JuliaInterpreter.prepare_thunk(Main, :(@static if ccall(:jl_get_UNAME, Any, ()) == :NoOS 1+1 end)), Nothing)
276276
@test isa(JuliaInterpreter.prepare_thunk(Main, :(Base.BaseDocs.@kw_str "using")), Nothing)
277+
278+
@testset "locals" begin
279+
f_locals(x::Int64, y::T, z::Vararg{Symbol}) where {T} = x
280+
frame = JuliaInterpreter.enter_call(f_locals, 1, 2.0, :a, :b)
281+
locals = JuliaInterpreter.locals(frame)
282+
@test JuliaInterpreter.Variable(1, :x, false) in locals
283+
@test JuliaInterpreter.Variable(2.0, :y, false) in locals
284+
@test JuliaInterpreter.Variable((:a, :b), :z, false) in locals
285+
@test JuliaInterpreter.Variable(Float64, :T, true) in locals
286+
287+
function f_multi(x)
288+
c = x
289+
x = 2
290+
x = 3
291+
x = 4
292+
return x
293+
end
294+
frame = JuliaInterpreter.enter_call(f_multi, 1)
295+
stack = [frame]
296+
locals = JuliaInterpreter.locals(frame)
297+
@test length(locals) == 2
298+
@test JuliaInterpreter.Variable(1, :x, false) in locals
299+
JuliaInterpreter.step_expr!(stack, frame)
300+
JuliaInterpreter.step_expr!(stack, frame)
301+
locals = JuliaInterpreter.locals(frame)
302+
@test length(locals) == 3
303+
@test JuliaInterpreter.Variable(1, :c, false) in locals
304+
JuliaInterpreter.step_expr!(stack, frame)
305+
locals = JuliaInterpreter.locals(frame)
306+
@test length(locals) == 3
307+
@test JuliaInterpreter.Variable(2, :x, false) in locals
308+
JuliaInterpreter.step_expr!(stack, frame)
309+
locals = JuliaInterpreter.locals(frame)
310+
@test length(locals) == 3
311+
@test JuliaInterpreter.Variable(3, :x, false) in locals
312+
end

0 commit comments

Comments
 (0)