To be able to edit code and run cells, you need to run the notebook yourself. Where would you like to run the notebook?

This notebook takes about 30 seconds to run.

In the cloud (experimental)

Binder is a free, open source service that runs scientific notebooks in the cloud! It will take a while, usually 2-7 minutes to get a session.

On your computer

(Recommended if you want to store your changes.)

  1. Copy the notebook URL:
  2. Run Pluto

    (Also see: How to install Julia and Pluto)

  3. Paste URL in the Open box

Frontmatter

If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and social media.

Author 1

ExpressionExplorer

This notebook is part of Pluto's source code.

👀 Reading hidden code
221 μs
👀 Reading hidden code
begin
if !@isdefined(PlutoRunner)
import ..PlutoRunner
end
import Markdown
end
3.7 ms

Two state objects

👀 Reading hidden code
184 μs
Vector{Symbol} (alias for Array{Symbol, 1})
👀 Reading hidden code


# TODO: use GlobalRef instead
FunctionName = Array{Symbol,1}
15.4 μs
👀 Reading hidden code
struct FunctionNameSignaturePair
canonicalized_head::Any
end
1.2 ms
👀 Reading hidden code
Base.:(==)(a::FunctionNameSignaturePair, b::FunctionNameSignaturePair) = a.name == b.name && a.canonicalized_head == b.canonicalized_head
547 μs
👀 Reading hidden code
Base.hash(a::FunctionNameSignaturePair, h::UInt) = hash(a.name, hash(a.canonicalized_head, h))
439 μs
SymbolsState
"SymbolsState trickles _down_ the ASTree: it carries referenced and defined variables from endpoints down to the root."
Base.@kwdef mutable struct SymbolsState
references::Set{Symbol} = Set{Symbol}()
assignments::Set{Symbol} = Set{Symbol}()
funccalls::Set{FunctionName} = Set{FunctionName}()
funcdefs::Dict{FunctionNameSignaturePair,SymbolsState} = Dict{FunctionNameSignaturePair,SymbolsState}()
end
👀 Reading hidden code
9.8 ms
ScopeState
begin
"ScopeState moves _up_ the ASTree: it carries scope information up towards the endpoints."
mutable struct ScopeState
inglobalscope::Bool
exposedglobals::Set{Symbol}
hiddenglobals::Set{Symbol}
definedfuncs::Set{Symbol}
end
ScopeState() = ScopeState(true, Set{Symbol}(), Set{Symbol}(), Set{Symbol}())
end
👀 Reading hidden code
2.1 ms
# The `union` and `union!` overloads define how two `SymbolsState`s or two `ScopeState`s are combined.

function Base.union(a::Dict{FunctionNameSignaturePair,SymbolsState}, bs::Dict{FunctionNameSignaturePair,SymbolsState}...)
union!(Dict{FunctionNameSignaturePair,SymbolsState}(), a, bs...)
end
👀 Reading hidden code
516 μs
function Base.union!(a::Dict{FunctionNameSignaturePair,SymbolsState}, bs::Dict{FunctionNameSignaturePair,SymbolsState}...)
for b in bs
for (k, v) in b
if haskey(a, k)
a[k] = union!(a[k], v)
else
a[k] = v
end
end
a
end
return a
end
👀 Reading hidden code
1.4 ms
function Base.union(a::SymbolsState, b::SymbolsState)
SymbolsState(a.references ∪ b.references, a.assignments ∪ b.assignments, a.funccalls ∪ b.funccalls, a.funcdefs ∪ b.funcdefs)
end
👀 Reading hidden code
601 μs
function Base.union!(a::SymbolsState, bs::SymbolsState...)
union!(a.references, (b.references for b in bs)...)
union!(a.assignments, (b.assignments for b in bs)...)
union!(a.funccalls, (b.funccalls for b in bs)...)
union!(a.funcdefs, (b.funcdefs for b in bs)...)
return a
end
👀 Reading hidden code
2.5 ms
function Base.union!(a::Tuple{FunctionName,SymbolsState}, bs::Tuple{FunctionName,SymbolsState}...)
a[1], union!(a[2], (b[2] for b in bs)...)
end
👀 Reading hidden code
932 μs
function Base.union(a::ScopeState, b::ScopeState)
SymbolsState(a.inglobalscope && b.inglobalscope, a.exposedglobals ∪ b.exposedglobals, a.hiddenglobals ∪ b.hiddenglobals)
end
👀 Reading hidden code
1.0 ms
function Base.union!(a::ScopeState, bs::ScopeState...)
a.inglobalscope &= all((b.inglobalscope for b in bs)...)
union!(a.exposedglobals, (b.exposedglobals for b in bs)...)
union!(a.hiddenglobals, (b.hiddenglobals for b in bs)...)
union!(a.definedfuncs, (b.definedfuncs for b in bs)...)
return a
end
👀 Reading hidden code
2.5 ms
function Base.:(==)(a::SymbolsState, b::SymbolsState)
a.references == b.references && a.assignments == b.assignments && a.funccalls == b.funccalls && a.funcdefs == b.funcdefs
end
👀 Reading hidden code
709 μs
Base.push!(x::Set) = x
👀 Reading hidden code
327 μs

Helper functions

👀 Reading hidden code
167 μs
# from the source code: https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm#L9
const modifiers = [:(+=), :(-=), :(*=), :(/=), :(//=), :(^=), :(÷=), :(%=), :(<<=), :(>>=), :(>>>=), :(&=), :(⊻=), :(≔), :(⩴), :(≕)]
👀 Reading hidden code
182 μs
const modifiers_dotprefixed = [Symbol('.' * String(m)) for m in modifiers]
👀 Reading hidden code
22.1 ms
will_assign_global (generic function with 1 method)
function will_assign_global(assignee::Symbol, scopestate::ScopeState)::Bool
(scopestate.inglobalscope || assignee ∈ scopestate.exposedglobals) && (assignee ∉ scopestate.hiddenglobals || assignee ∈ scopestate.definedfuncs)
end
👀 Reading hidden code
716 μs
will_assign_global (generic function with 2 methods)
function will_assign_global(assignee::Array{Symbol,1}, scopestate::ScopeState)::Bool
if length(assignee) == 0
false
elseif length(assignee) > 1
scopestate.inglobalscope
else
will_assign_global(assignee[1], scopestate)
end
end
👀 Reading hidden code
756 μs
get_global_assignees (generic function with 1 method)
function get_global_assignees(assignee_exprs, scopestate::ScopeState)::Set{Symbol}
global_assignees = Set{Symbol}()
for ae in assignee_exprs
if isa(ae, Symbol)
will_assign_global(ae, scopestate) && push!(global_assignees, ae)
else
if ae.head == :(::)
will_assign_global(ae.args[1], scopestate) && push!(global_assignees, ae.args[1])
else
@warn "Unknown assignee expression" ae
end
end
end
return global_assignees
end
👀 Reading hidden code
3.0 ms
get_assignees (generic function with 1 method)
function get_assignees(ex::Expr)::FunctionName
if ex.head == :tuple
# e.g. (x, y) in the ex (x, y) = (1, 23)
union!(Symbol[], get_assignees.(ex.args)...)
# filter(s->s isa Symbol, ex.args)
elseif ex.head == :(::)
# TODO: type is referenced
Symbol[ex.args[1]]
elseif ex.head == :ref || ex.head == :(.)
Symbol[]
else
@warn "unknown use of `=`. Assignee is unrecognised." ex
Symbol[]
end
end
👀 Reading hidden code
2.9 ms
get_assignees (generic function with 3 methods)
# e.g. x = 123, but ignore _ = 456
get_assignees(ex::Symbol) = all_underscores(ex) ? Symbol[] : Symbol[ex]
👀 Reading hidden code
459 μs
get_assignees (generic function with 2 methods)
# When you assign to a datatype like Int, String, or anything bad like that
# e.g. 1 = 2
# This is parsable code, so we have to treat it
get_assignees(::Any) = Symbol[]
👀 Reading hidden code
399 μs
all_underscores (generic function with 1 method)
all_underscores(s::Symbol) = all(isequal('_'), string(s))
👀 Reading hidden code
391 μs
uncurly!

Turn :(A{T}) into :A.

# TODO: this should return a FunctionName, and use `split_funcname`.
"Turn :(A{T}) into :A."
function uncurly!(ex::Expr, scopestate::ScopeState)::Symbol
@assert ex.head == :curly
push!(scopestate.hiddenglobals, (a for a in ex.args[2:end] if a isa Symbol)...)
Symbol(ex.args[1])
end
👀 Reading hidden code
1.6 ms
uncurly! (generic function with 2 methods)
uncurly!(ex::Expr)::Symbol = ex.args[1]
👀 Reading hidden code
490 μs
uncurly! (generic function with 4 methods)
uncurly!(s::Symbol, scopestate=nothing)::Symbol = s
👀 Reading hidden code
662 μs
split_funcname

Turn :(Base.Submodule.f) into [:Base, :Submodule, :f] and :f into [:f].

"Turn `:(Base.Submodule.f)` into `[:Base, :Submodule, :f]` and `:f` into `[:f]`."
function split_funcname(funcname_ex::Expr)::FunctionName
if funcname_ex.head == :(.)
vcat(split_funcname.(funcname_ex.args)...)
else
# a call to a function that's not a global, like calling an array element: `funcs[12]()`
# TODO: explore symstate!
Symbol[]
end
end
👀 Reading hidden code
868 μs
split_funcname (generic function with 2 methods)
function split_funcname(funcname_ex::QuoteNode)::FunctionName
split_funcname(funcname_ex.value)
end
👀 Reading hidden code
435 μs
split_funcname (generic function with 3 methods)
function split_funcname(funcname_ex::GlobalRef)::FunctionName
split_funcname(funcname_ex.name)
end
👀 Reading hidden code
469 μs
split_funcname (generic function with 5 methods)
function split_funcname(funcname_ex::Symbol)::FunctionName
Symbol[funcname_ex |> without_dotprefix |> without_dotsuffix]
end
👀 Reading hidden code
451 μs
is_just_dots (generic function with 1 method)
function is_just_dots(ex::Expr)
ex.head == :(.) && all(is_just_dots, ex.args)
end
👀 Reading hidden code
466 μs
is_just_dots (generic function with 2 methods)
is_just_dots(::Union{QuoteNode,Symbol,GlobalRef}) = true
👀 Reading hidden code
410 μs
is_just_dots (generic function with 3 methods)
is_just_dots(::Any) = false
👀 Reading hidden code
350 μs
split_funcname (generic function with 4 methods)
# this includes GlobalRef - it's fine that we don't recognise it, because you can't assign to a globalref?
function split_funcname(::Any)::FunctionName
Symbol[]
end
👀 Reading hidden code
417 μs
without_dotprefix

Turn Symbol(".+") into :(+)

"""Turn `Symbol(".+")` into `:(+)`"""
function without_dotprefix(funcname::Symbol)::Symbol
fn_str = String(funcname)
if length(fn_str) > 0 && fn_str[1] == '.'
Symbol(fn_str[2:end])
else
funcname
end
end
👀 Reading hidden code
957 μs
without_dotsuffix

Turn Symbol("sqrt.") into :sqrt

"""Turn `Symbol("sqrt.")` into `:sqrt`"""
function without_dotsuffix(funcname::Symbol)::Symbol
fn_str = String(funcname)
if length(fn_str) > 0 && fn_str[end] == '.'
Symbol(fn_str[1:end - 1])
else
funcname
end
end
👀 Reading hidden code
1.0 ms
join_funcname_parts

Turn Symbol[:Module, :func] into Symbol("Module.func").

This is not the same as the expression :(Module.func), but is used to identify the function name using a single Symbol (like normal variables). This means that it is only the inverse of ExpressionExplorer.split_funcname iff length(parts) ≤ 1.

"""Turn `Symbol[:Module, :func]` into Symbol("Module.func").

This is **not** the same as the expression `:(Module.func)`, but is used to identify the function name using a single `Symbol` (like normal variables).
This means that it is only the inverse of `ExpressionExplorer.split_funcname` iff `length(parts) ≤ 1`."""
function join_funcname_parts(parts::FunctionName)::Symbol
join(parts .|> String, ".") |> Symbol
end
👀 Reading hidden code
704 μs
is_joined_funcname (generic function with 1 method)
# this is stupid -- désolé
function is_joined_funcname(joined::Symbol)
occursin('.', String(joined))
end
👀 Reading hidden code
412 μs
assign_to_kw (generic function with 1 method)
assign_to_kw(e::Expr) = e.head == :(=) ? Expr(:kw, e.args...) : e
👀 Reading hidden code
544 μs
assign_to_kw (generic function with 2 methods)
assign_to_kw(x::Any) = x
👀 Reading hidden code
342 μs
strip_indexing

Turn A[i] * B[j,K[l+m]] into A[0] * B[0,K[0+0]] to hide loop indices

"Turn `A[i] * B[j,K[l+m]]` into `A[0] * B[0,K[0+0]]` to hide loop indices"
function strip_indexing(x, inside::Bool=false)
if Meta.isexpr(x, :ref)
Expr(:ref, strip_indexing(x.args[1]), strip_indexing.(x.args[2:end], true)...)
elseif Meta.isexpr(x, :call)
Expr(x.head, x.args[1], strip_indexing.(x.args[2:end], inside)...)
elseif x isa Symbol && inside
0
else
x
end
end
👀 Reading hidden code
2.2 ms

Main recursive function

Spaghetti code for a spaghetti problem 🍝

👀 Reading hidden code
232 μs
explore! (generic function with 1 method)

# Possible leaf: value
# Like: a = 1
# 1 is a value (Int64)
function explore!(value, scopestate::ScopeState)::SymbolsState
# includes: LineNumberNode, Int64, String,
return SymbolsState()
end
👀 Reading hidden code
428 μs
explore! (generic function with 2 methods)
# Possible leaf: symbol
# Like a = x
# x is a symbol
# We handle the assignment separately, and explore!(:a, ...) will not be called.
# Therefore, this method only handles _references_, which are added to the symbolstate, depending on the scopestate.
function explore!(sym::Symbol, scopestate::ScopeState)::SymbolsState
if sym ∈ scopestate.hiddenglobals
SymbolsState()
else
SymbolsState(references=Set([sym]))
end
end
👀 Reading hidden code
756 μs
explore_inner_scoped (generic function with 1 method)
begin
# General recursive method. Is never a leaf.
# Modifies the `scopestate`.
function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
if ex.head == :(=)
# Does not create scope
if ex.args[1] isa Expr && (ex.args[1].head == :call || ex.args[1].head == :where || (ex.args[1].head == :(::) && ex.args[1].args[1] isa Expr && ex.args[1].args[1].head == :call))
# f(x, y) = x + y
# Rewrite to:
# function f(x, y) x + y end
return explore!(Expr(:function, ex.args...), scopestate)
end
val = ex.args[2]
# Handle generic types assignments A{B} = C{B, Int}
if ex.args[1] isa Expr && ex.args[1].head == :curly
assignees, symstate = explore_funcdef!(ex.args[1], scopestate)
innersymstate = union!(symstate, explore!(val, scopestate))
else
assignees = get_assignees(ex.args[1])
symstate = innersymstate = explore!(val, scopestate)
end
global_assignees = get_global_assignees(assignees, scopestate)
# If we are _not_ assigning a global variable, then this symbol hides any global definition with that name
push!(scopestate.hiddenglobals, setdiff(assignees, global_assignees)...)
assigneesymstate = explore!(ex.args[1], scopestate)
push!(scopestate.hiddenglobals, global_assignees...)
push!(symstate.assignments, global_assignees...)
push!(symstate.references, setdiff(assigneesymstate.references, global_assignees)...)
filter!(!all_underscores, symstate.references) # Never record _ as a reference
return symstate
elseif ex.head in modifiers
# We change: a[1] += 123
# to: a[1] = a[1] + 123
# We transform the modifier back to its operator
# for when users redefine the + function
operator = let
s = string(ex.head)
Symbol(s[1:prevind(s, lastindex(s))])
end
expanded_expr = Expr(:(=), ex.args[1], Expr(:call, operator, ex.args[1], ex.args[2]))
return explore!(expanded_expr, scopestate)
elseif ex.head in modifiers_dotprefixed
# We change: a[1] .+= 123
# to: a[1] .= a[1] + 123
operator = Symbol(string(ex.head)[2:end - 1])
expanded_expr = Expr(:(.=), ex.args[1], Expr(:call, operator, ex.args[1], ex.args[2]))
return explore!(expanded_expr, scopestate)
elseif ex.head == :let || ex.head == :for || ex.head == :while
# Creates local scope
return explore_inner_scoped(ex, scopestate)
elseif ex.head == :filter
# In a filter, the assignment is the second expression, the condition the first
return mapfoldr(a -> explore!(a, scopestate), union!, ex.args, init=SymbolsState())
elseif ex.head == :generator
# Creates local scope
# In a `generator`, a single expression is followed by the iterator assignments.
# In a `for`, this expression comes at the end.
# This is not strictly the normal form of a `for` but that's okay
return explore!(Expr(:for, ex.args[2:end]..., ex.args[1]), scopestate)
elseif ex.head == :macrocall
# Does not create sccope
new_ex = maybe_macroexpand(ex)
newnew_ex = Meta.isexpr(new_ex, :macrocall) ? Expr(:call, new_ex.args...) : new_ex
return explore!(newnew_ex, scopestate)
elseif ex.head == :call
# Does not create scope
if is_just_dots(ex.args[1])
funcname = ex.args[1] |> split_funcname
symstate = if length(funcname) == 0
explore!(ex.args[1], scopestate)
elseif length(funcname) == 1
if funcname[1] ∈ scopestate.hiddenglobals
SymbolsState()
else
SymbolsState(funccalls=Set{FunctionName}([funcname]))
end
else
SymbolsState(references=Set{Symbol}([funcname[1]]), funccalls=Set{FunctionName}([funcname]))
end
# Explore code inside function arguments:
union!(symstate, explore!(Expr(:block, ex.args[2:end]...), scopestate))
return symstate
else
return explore!(Expr(:block, ex.args...), scopestate)
end
elseif ex.head == :kw
return explore!(ex.args[2], scopestate)
elseif ex.head == :struct
# Creates local scope
structnameexpr = ex.args[2]
structfields = ex.args[3].args
equiv_func = Expr(:function, Expr(:call, structnameexpr, structfields...), Expr(:block, nothing))
# struct should always be in Global state
globalscopestate = deepcopy(scopestate)
globalscopestate.inglobalscope = true
# we register struct definitions as both a variable and a function. This is because deleting a struct is trickier than just deleting its methods.
inner_symstate = explore!(equiv_func, globalscopestate)
structname = first(keys(inner_symstate.funcdefs)).name |> join_funcname_parts
push!(inner_symstate.assignments, structname)
return inner_symstate
elseif ex.head == :abstract
equiv_func = Expr(:function, ex.args...)
inner_symstate = explore!(equiv_func, scopestate)
abstracttypename = first(keys(inner_symstate.funcdefs)).name |> join_funcname_parts
push!(inner_symstate.assignments, abstracttypename)
return inner_symstate
elseif ex.head == :function || ex.head == :macro
symstate = SymbolsState()
# Creates local scope
funcroot = ex.args[1]
# Because we are entering a new scope, we create a copy of the current scope state, and run it through the expressions.
innerscopestate = deepcopy(scopestate)
innerscopestate.inglobalscope = false
funcname, innersymstate = explore_funcdef!(funcroot, innerscopestate)
# Macro are called using @funcname, but defined with funcname. We need to change that in our scopestate
# (The `!= 0` is for when the function named couldn't be parsed)
if ex.head == :macro && length(funcname) != 0
setdiff!(innerscopestate.hiddenglobals, funcname)
funcname = Symbol[Symbol("@$(funcname[1])")]
push!(innerscopestate.hiddenglobals, funcname...)
end
union!(innersymstate, explore!(Expr(:block, ex.args[2:end]...), innerscopestate))
funcnamesig = FunctionNameSignaturePair(funcname, canonalize(funcroot))
if will_assign_global(funcname, scopestate)
symstate.funcdefs[funcnamesig] = innersymstate
if length(funcname) == 1
push!(scopestate.definedfuncs, funcname[end])
push!(scopestate.hiddenglobals, funcname[end])
elseif length(funcname) > 1
push!(symstate.references, funcname[end - 1]) # reference the module of the extended function
end
else
# The function is not defined globally. However, the function can still modify the global scope or reference globals, e.g.
# let
# function f(x)
# global z = x + a
# end
# f(2)
# end
# so we insert the function's inner symbol state here, as if it was a `let` block.
symstate = innersymstate
end
return symstate
elseif ex.head == :try
symstate = SymbolsState()
# Handle catch first
if ex.args[3] != false
union!(symstate, explore_inner_scoped(ex.args[3], scopestate))
# If we catch a symbol, it could shadow a global reference, remove it
if ex.args[2] != false
setdiff!(symstate.references, Symbol[ex.args[2]])
end
end
# Handle the try block
union!(symstate, explore_inner_scoped(ex.args[1], scopestate))
# Finally, handle finally
if length(ex.args) == 4
union!(symstate, explore_inner_scoped(ex.args[4], scopestate))
end
return symstate
elseif ex.head == :(->)
# Creates local scope
tempname = Symbol("anon", rand(UInt64))
# We will rewrite this to a normal function definition, with a temporary name
funcroot = ex.args[1]
args_ex = if funcroot isa Symbol || (funcroot isa Expr && funcroot.head == :(::))
[funcroot]
elseif funcroot.head == :tuple || funcroot.head == :(...) || funcroot.head == :block
funcroot.args
else
@error "Unknown lambda type"
end
equiv_func = Expr(:function, Expr(:call, tempname, args_ex...), ex.args[2])
return explore!(equiv_func, scopestate)
elseif ex.head == :global
# Does not create scope
# We have one of:
# global x;
# global x = 1;
# global x += 1;
# where x can also be a tuple:
# global a,b = 1,2
globalisee = ex.args[1]
if isa(globalisee, Symbol)
push!(scopestate.exposedglobals, globalisee)
return SymbolsState()
elseif isa(globalisee, Expr)
# temporarily set inglobalscope to true
old = scopestate.inglobalscope
scopestate.inglobalscope = true
result = explore!(globalisee, scopestate)
scopestate.inglobalscope = old
return result
else
@error "unknown global use" ex
return explore!(globalisee, scopestate)
end
return symstate
elseif ex.head == :local
# Does not create scope
localisee = ex.args[1]
if isa(localisee, Symbol)
push!(scopestate.hiddenglobals, localisee)
return SymbolsState()
elseif isa(localisee, Expr) && (localisee.head == :(=) || localisee.head in modifiers)
push!(scopestate.hiddenglobals, get_assignees(localisee.args[1])...)
return explore!(localisee, scopestate)
else
@warn "unknown local use" ex
return explore!(localisee, scopestate)
end
elseif ex.head == :tuple
# Does not create scope
# There are three (legal) cases:
# 1. Creating a tuple:
# (a, b, c)
# 2. Creating a named tuple:
# (a=1, b=2, c=3)
# 3. Multiple assignments
# a,b,c = 1,2,3
# This parses to:
# head = :tuple
# args = [:a, :b, :(c=1), :2, :3]
#
# 🤔
# we turn it into two expressions:
#
# (a, b) = (2, 3)
# (c = 1)
#
# and explore those :)
indexoffirstassignment = findfirst(a -> isa(a, Expr) && a.head == :(=), ex.args)
if indexoffirstassignment !== nothing
# we have one of two cases, see next `if`
indexofsecondassignment = findnext(a -> isa(a, Expr) && a.head == :(=), ex.args, indexoffirstassignment + 1)
if length(ex.args) == 1 || indexofsecondassignment !== nothing
# 2.
# we have a named tuple, e.g. (a=1, b=2)
new_args = map(ex.args) do a
(a isa Expr && a.head == :(=)) ? a.args[2] : a
end
return explore!(Expr(:block, new_args...), scopestate)
else
# 3.
# we have a tuple assignment, e.g. `a, (b, c) = [1, [2, 3]]`
before = ex.args[1:indexoffirstassignment - 1]
after = ex.args[indexoffirstassignment + 1:end]
symstate_middle = explore!(ex.args[indexoffirstassignment], scopestate)
symstate_outer = explore!(Expr(:(=), Expr(:tuple, before...), Expr(:block, after...)), scopestate)
return union!(symstate_middle, symstate_outer)
end
else
# 1.
# good ol' tuple
return explore!(Expr(:block, ex.args...), scopestate)
end
elseif ex.head == :(.) && ex.args[2] isa Expr && ex.args[2].head == :tuple
# pointwise function call, e.g. sqrt.(nums)
# we rewrite to a regular call
return explore!(Expr(:call, ex.args[1], ex.args[2].args...), scopestate)
elseif ex.head == :using || ex.head == :import
imports = if ex.args[1].head == :(:)
ex.args[1].args[2:end]
else
ex.args
end
packagenames = map(e -> e.args[end], imports)
return SymbolsState(assignments=Set{Symbol}(packagenames))
elseif ex.head == :quote
# We ignore contents
return SymbolsState()
elseif ex.head == :module
# We ignore contents; the module name is a definition
return SymbolsState(assignments=Set{Symbol}([ex.args[2]]))
else
# fallback, includes:
# begin, block, do, toplevel, const
# (and hopefully much more!)
# Does not create scope (probably)
return mapfoldl(a -> explore!(a, scopestate), union!, ex.args, init=SymbolsState())
end
end
"Return the function name and the SymbolsState from argument defaults. Add arguments as hidden globals to the `scopestate`.
Is also used for `struct` and `abstract`."
function explore_funcdef!(ex::Expr, scopestate::ScopeState)::Tuple{FunctionName,SymbolsState}
if ex.head == :call
params_to_explore = ex.args[2:end]
# Using the keyword args syntax f(;y) the :parameters node is the first arg in the AST when it should
# be explored last. We change from (parameters, ...) to (..., parameters)
if length(params_to_explore) >= 2 && params_to_explore[1] isa Expr && params_to_explore[1].head == :parameters
params_to_explore = [params_to_explore[2:end]..., params_to_explore[1]]
end
# Handle struct as callables, `(obj::MyType)(a, b) = ...`
# or `function (obj::MyType)(a, b) ...; end` by rewriting it as:
# function MyType(obj, a, b) ...; end
funcroot = ex.args[1]
if funcroot isa Expr && funcroot.head == :(::)
return explore_funcdef!(Expr(:call, reverse(funcroot.args)..., params_to_explore...), scopestate)
end
# get the function name
name, symstate = explore_funcdef!(funcroot, scopestate)
# and explore the function arguments
return mapfoldl(a -> explore_funcdef!(a, scopestate), union!, params_to_explore, init=(name, symstate))
elseif ex.head == :(::) || ex.head == :kw || ex.head == :(=)
# account for unnamed params, like in f(::Example) = 1
if ex.head == :(::) && length(ex.args) == 1
symstate = explore!(ex.args[1], scopestate)
return Symbol[], symstate
end
# recurse
name, symstate = explore_funcdef!(ex.args[1], scopestate)
if length(ex.args) > 1
# use `explore!` (not `explore_funcdef!`) to explore the argument's default value - these can contain arbitrary expressions
union!(symstate, explore!(ex.args[2], scopestate))
end
return name, symstate
elseif ex.head == :where
# function(...) where {T, S <: R, U <: A.B}
# supertypes `R` and `A.B` are referenced
supertypes_symstate = SymbolsState()
for a in ex.args[2:end]
name, inner_symstate = explore_funcdef!(a, scopestate)
if length(name) == 1
push!(scopestate.hiddenglobals, name[1])
end
union!(supertypes_symstate, inner_symstate)
end
# recurse
name, symstate = explore_funcdef!(ex.args[1], scopestate)
union!(symstate, supertypes_symstate)
return name, symstate
elseif ex.head == :(<:)
# for use in `struct` and `abstract`
name = uncurly!(ex.args[1], scopestate)
symstate = if length(ex.args) == 1
SymbolsState()
else
explore!(ex.args[2], scopestate)
end
return Symbol[name], symstate
elseif ex.head == :curly
name = uncurly!(ex, scopestate)
return Symbol[name], SymbolsState()
elseif ex.head == :parameters || ex.head == :tuple
return mapfoldl(a -> explore_funcdef!(a, scopestate), union!, ex.args, init=(Symbol[], SymbolsState()))
elseif ex.head == :(.)
return split_funcname(ex), SymbolsState()
elseif ex.head == :(...)
return explore_funcdef!(ex.args[1], scopestate)
else
return Symbol[], explore!(ex, scopestate)
end
end
function explore_inner_scoped(ex::Expr, scopestate::ScopeState)::SymbolsState
# Because we are entering a new scope, we create a copy of the current scope state, and run it through the expressions.
innerscopestate = deepcopy(scopestate)
innerscopestate.inglobalscope = false

return mapfoldl(a -> explore!(a, innerscopestate), union!, ex.args, init=SymbolsState())
end
end
👀 Reading hidden code
49.4 ms
explore_funcdef! (generic function with 1 method)
function explore_funcdef!(ex::QuoteNode, scopestate::ScopeState)::Tuple{FunctionName,SymbolsState}
explore_funcdef!(ex.value, scopestate)
end
👀 Reading hidden code
479 μs
explore_funcdef! (generic function with 2 methods)
function explore_funcdef!(ex::Symbol, scopestate::ScopeState)::Tuple{FunctionName,SymbolsState}
push!(scopestate.hiddenglobals, ex)
Symbol[ex |> without_dotprefix |> without_dotsuffix], SymbolsState()
end
👀 Reading hidden code
657 μs
explore_funcdef! (generic function with 3 methods)
function explore_funcdef!(::Any, ::ScopeState)::Tuple{FunctionName,SymbolsState}
Symbol[], SymbolsState()
end
👀 Reading hidden code
473 μs
const can_macroexpand_no_bind = Set(Symbol.(["@md_str", "Markdown.@md_str", "@gensym", "Base.@gensym", "@kwdef", "Base.@kwdef", "@enum", "Base.@enum", "@cmd"]))
👀 Reading hidden code
49.8 ms
const can_macroexpand = can_macroexpand_no_bind ∪ Set(Symbol.(["@bind", "PlutoRunner.@bind"]))
👀 Reading hidden code
236 μs
macro_kwargs_as_kw (generic function with 1 method)
macro_kwargs_as_kw(ex::Expr) = Expr(:macrocall, ex.args[1:3]..., assign_to_kw.(ex.args[4:end])...)
👀 Reading hidden code
649 μs
symbolics_mockexpand (generic function with 1 method)
function symbolics_mockexpand(s::Any)
# goofy implementation of the syntax described in https://symbolics.juliasymbolics.org/dev/manual/variables/
if Meta.isexpr(s, :ref, 2)
:($(s.args[1]) = $(s.args[2]))
elseif Meta.isexpr(s, :call, 2)
second = s.args[2] === Symbol("..") ? 123 : s.args[2]
:($(symbolics_mockexpand(s.args[1])); $(second) = 123)
elseif s isa Symbol
:($(s) = 123)
else
nothing
end
end
👀 Reading hidden code
1.2 ms
is_symbolics_arg (generic function with 1 method)
is_symbolics_arg(s) = symbolics_mockexpand(s) !== nothing
👀 Reading hidden code
387 μs
maybe_untuple (generic function with 1 method)
maybe_untuple(es) = if length(es) == 1 && Meta.isexpr(first(es), :tuple)
first(es).args
else
es
end
👀 Reading hidden code
565 μs
maybe_macroexpand

If the macro is known to Pluto, expand or 'mock expand' it, if not, return the expression.

Macros can transform the expression into anything - the best way to treat them is to macroexpand. The problem is that the macro is only available on the worker process, see https://github.com/fonsp/Pluto.jl/issues/196

"""
If the macro is known to Pluto, expand or 'mock expand' it, if not, return the expression.

Macros can transform the expression into anything - the best way to treat them is to `macroexpand`. The problem is that the macro is only available on the worker process, see https://github.com/fonsp/Pluto.jl/issues/196
"""
function maybe_macroexpand(ex::Expr; recursive=false, expand_bind=true)
result = if ex.head === :macrocall
funcname = ex.args[1] |> split_funcname
funcname_joined = join_funcname_parts(funcname)

args = ex.args[3:end]
if funcname_joined ∈ (expand_bind ? can_macroexpand : can_macroexpand_no_bind)
expanded = macroexpand(PlutoRunner, ex; recursive=false)
Expr(:call, ex.args[1], expanded)

elseif !isempty(args) && Meta.isexpr(args[1], :(:=))
ex = macro_kwargs_as_kw(ex)
# macros like @einsum C[i] := A[i,j] are assignment to C, illegal syntax without macro
ein = args[1]
left = if Meta.isexpr(ein.args[1], :ref)
# assign to the symbol, and save LHS indices as fake RHS argument
ex = Expr(ex.head, ex.args..., Expr(:ref, :Float64, ein.args[1].args[2:end]...))
ein.args[1].args[1]
else
ein.args[1] # scalar case `c := A[i,j]`
end
ein_done = Expr(:(=), left, strip_indexing.(ein.args[2:end])...) # i,j etc. are local
Expr(:call, ex.args[1:2]..., ein_done, strip_indexing.(ex.args[4:end])...)
elseif !isempty(args) && funcname_joined === Symbol("@ode_def")
if args[1] isa Symbol
:($(args[1]) = @ode_def 123)
else
:(@ode_def)
end
elseif !isempty(args) && (funcname_joined === Symbol("@functor") || funcname_joined === Symbol("Flux.@functor"))
Expr(:macrocall, ex.args[1:2]..., :($(args[1]) = 123), ex.args[4:end]...)
elseif !isempty(args) && (funcname_joined === Symbol("@variables") || funcname_joined === Symbol("Symbolics.@variables")) && all(is_symbolics_arg, maybe_untuple(args))
Expr(:macrocall, ex.args[1:2]..., symbolics_mockexpand.(maybe_untuple(args))...)
# elseif length(ex.args) >= 4 && (funcname_joined === Symbol("@variable") || funcname_joined === Symbol("JuMP.@variable"))
# if Meta.isexpr(ex.args[4], :comparison)
# parts = ex.args[4].args[1:2:end]
# if length(parts) == 2
# foldl(parts) do (e,next)
# :($(e) = $(next))
# end
# elseif Meta.isexpr(ex.args[4], :block)

# end


# Expr(:macrocall, ex.args[1:3]..., )
# add more macros here
elseif length(args) ≥ 2 && ex.args[1] != GlobalRef(Core, Symbol("@doc"))
# for macros like @test a ≈ b atol=1e-6, read assignment in 2nd & later arg as keywords
macro_kwargs_as_kw(ex)
else
ex
end
else
ex
end

if recursive && (result isa Expr)
Expr(result.head, maybe_macroexpand.(result.args; recursive=recursive, expand_bind=expand_bind)...)
else
result
end
end
👀 Reading hidden code
5.8 ms
maybe_macroexpand (generic function with 2 methods)
maybe_macroexpand(ex::Any; kwargs...) = ex
👀 Reading hidden code
1.0 ms

Canonicalize function definitions

👀 Reading hidden code
166 μs
canonalize

Turn a function definition expression (Expr) into a "canonical" form, in the sense that two methods that would evaluate to the same method signature have the same canonical form. Part of a solution to https://github.com/fonsp/Pluto.jl/issues/177. Such a canonical form cannot be achieved statically with 100% correctness (impossible), but we can make it good enough to be practical.

Wait, "evaluate to the same method signature"?

In Pluto, you cannot do definitions of the same global variable in different cells. This is needed for reactivity to work, and it avoid ambiguous notebooks and stateful stuff. This rule used to also apply to functions: you had to place all methods of a function in one cell. (Go and read more about methods in Julia if you haven't already.) But this is quite annoying, especially because multiple dispatch is so important in Julia code. So we allow methods of the same function to be defined across multiple cells, but we still want to throw errors when you define multiple methods with the same signature, because one overrides the other. For example:

julia> f(x) = 1
f (generic function with 1 method)

julia> f(x) = 2
f (generic function with 1 method)
``

After adding the second method, the function still has only 1 method. This is because the second definition overrides the first one, instead of being added to the method table. This example should be illegal in Julia, for the same reason that `f = 1` and `f = 2` is illegal. So our problem is: how do we know that two cells will define overlapping methods? 

Ideally, we would just evaluate the user's code and **count methods** afterwards, letting Julia do the work. Unfortunately, we need to know this info _before_ we run cells, otherwise we don't know in which order to run a notebook! There are ways to break this circle, but it would complicate our process quite a bit.

Instead, we will do _static analysis_ on the function definition expressions to determine whether they overlap. This is non-trivial. For example, `f(x)` and `f(y::Any)` define the same method. Trickier examples are here: https://github.com/fonsp/Pluto.jl/issues/177#issuecomment-645039993

# Wait, "function definition expressions"?
For example:

julia e = :(function f(x::Int, y::String) x + y end)

dump(e, maxdepth=2)

#= gives:

Expr head: Symbol function args: Array{Any}((2,)) 1: Expr 2: Expr =#


This first arg is the function head:

julia e.args[1] == :(f(x::Int, y::String))


# Mathematics
Our problem is to find a way to compute the equivalence relation ~ on `H × H`, with `H` the set of function head expressions, defined as:

`a ~ b` iff evaluating both expressions results in a function with exactly one method.

_(More precisely, evaluating `Expr(:function, x, Expr(:block))` with `x ∈ {a, b}`.)_

The equivalence sets are isomorphic to the set of possible Julia methods.

Instead of finding a closed form algorithm for `~`, we search for a _canonical form_: a function `canonical: H -> H` that chooses one canonical expression per equivalence class. It has the property 
    
`canonical(a) = canonical(b)` implies `a ~ b`.

We use this **canonical form** of the function's definition expression as its "signature". We compare these canonical forms when determining whether two function expressions will result in overlapping methods.

# Example

julia e1 = :(f(x, z::Any)) e2 = :(g(x, y))

canonalize(e1) == canonalize(e2)

julia e1 = :(f(x)) e2 = :(g(x, y))

canonalize(e1) != canonalize(e2)

julia e1 = :(f(a::X, b::wow(ie), c, d...; e=f) where T) e2 = :(g(z::X, z::wow(ie), z::Any, z... ) where T)

canonalize(e1) == canonalize(e2) ```

"""
Turn a function definition expression (`Expr`) into a "canonical" form, in the sense that two methods that would evaluate to the same method signature have the same canonical form. Part of a solution to https://github.com/fonsp/Pluto.jl/issues/177. Such a canonical form cannot be achieved statically with 100% correctness (impossible), but we can make it good enough to be practical.


# Wait, "evaluate to the same method signature"?

In Pluto, you cannot do definitions of **the same global variable** in different cells. This is needed for reactivity to work, and it avoid ambiguous notebooks and stateful stuff. This rule used to also apply to functions: you had to place all methods of a function in one cell. (Go and read more about methods in Julia if you haven't already.) But this is quite annoying, especially because multiple dispatch is so important in Julia code. So we allow methods of the same function to be defined across multiple cells, but we still want to throw errors when you define **multiple methods with the same signature**, because one overrides the other. For example:
```julia
julia> f(x) = 1
f (generic function with 1 method)

julia> f(x) = 2
f (generic function with 1 method)
``

After adding the second method, the function still has only 1 method. This is because the second definition overrides the first one, instead of being added to the method table. This example should be illegal in Julia, for the same reason that `f = 1` and `f = 2` is illegal. So our problem is: how do we know that two cells will define overlapping methods?

Ideally, we would just evaluate the user's code and **count methods** afterwards, letting Julia do the work. Unfortunately, we need to know this info _before_ we run cells, otherwise we don't know in which order to run a notebook! There are ways to break this circle, but it would complicate our process quite a bit.

Instead, we will do _static analysis_ on the function definition expressions to determine whether they overlap. This is non-trivial. For example, `f(x)` and `f(y::Any)` define the same method. Trickier examples are here: https://github.com/fonsp/Pluto.jl/issues/177#issuecomment-645039993

# Wait, "function definition expressions"?
For example:

```julia
e = :(function f(x::Int, y::String)
x + y
end)

dump(e, maxdepth=2)

#=
gives:

Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
2: Expr
=#
```

This first arg is the function head:

```julia
e.args[1] == :(f(x::Int, y::String))
```

# Mathematics
Our problem is to find a way to compute the equivalence relation ~ on `H × H`, with `H` the set of function head expressions, defined as:

`a ~ b` iff evaluating both expressions results in a function with exactly one method.

_(More precisely, evaluating `Expr(:function, x, Expr(:block))` with `x ∈ {a, b}`.)_

The equivalence sets are isomorphic to the set of possible Julia methods.

Instead of finding a closed form algorithm for `~`, we search for a _canonical form_: a function `canonical: H -> H` that chooses one canonical expression per equivalence class. It has the property
`canonical(a) = canonical(b)` implies `a ~ b`.

We use this **canonical form** of the function's definition expression as its "signature". We compare these canonical forms when determining whether two function expressions will result in overlapping methods.

# Example
```julia
e1 = :(f(x, z::Any))
e2 = :(g(x, y))

canonalize(e1) == canonalize(e2)
```

```julia
e1 = :(f(x))
e2 = :(g(x, y))

canonalize(e1) != canonalize(e2)
```

```julia
e1 = :(f(a::X, b::wow(ie), c, d...; e=f) where T)
e2 = :(g(z::X, z::wow(ie), z::Any, z... ) where T)

canonalize(e1) == canonalize(e2)
```
"""
function canonalize(ex::Expr)
if ex.head == :where
Expr(:where, canonalize(ex.args[1]), ex.args[2:end]...)
elseif ex.head == :call || ex.head == :tuple
skip_index = ex.head == :call ? 2 : 1
# ex.args[1], if ex.head == :call this is the function name, we dont want it

interesting = filter(ex.args[skip_index:end]) do arg
!(arg isa Expr && arg.head == :parameters)
end
hide_argument_name.(interesting)
elseif ex.head == :(::)
canonalize(ex.args[1])
elseif ex.head == :curly || ex.head == :(<:)
# for struct definitions, which we hackily treat as functions
nothing
else
@error "Failed to canonalize this strange looking function" ex
nothing
end
end
👀 Reading hidden code
4.5 ms
canonalize (generic function with 1 method)
# for `function g end`
canonalize(::Symbol) = nothing
👀 Reading hidden code
885 μs
hide_argument_name (generic function with 1 method)
function hide_argument_name(ex::Expr)
if ex.head == :(::) && length(ex.args) > 1
Expr(:(::), nothing, ex.args[2:end]...)
elseif ex.head == :(...)
Expr(:(...), hide_argument_name(ex.args[1]))
elseif ex.head == :kw
Expr(:kw, hide_argument_name(ex.args[1]), nothing)
else
ex
end
end
👀 Reading hidden code
1.1 ms
hide_argument_name (generic function with 2 methods)
hide_argument_name(::Symbol) = Expr(:(::), nothing, :Any)
👀 Reading hidden code
395 μs
hide_argument_name (generic function with 3 methods)
hide_argument_name(x::Any) = x
👀 Reading hidden code
329 μs

Utility functions

👀 Reading hidden code
161 μs
compute_symbolreferences

Get the global references, assignment, function calls and function defintions inside an arbitrary expression.

"Get the global references, assignment, function calls and function defintions inside an arbitrary expression."
function compute_symbolreferences(ex::Any)::SymbolsState
symstate = explore!(ex, ScopeState())

# We do something special to account for recursive functions:
# If a function `f` calls a function `g`, and both are defined inside this cell, the reference to `g` inside the symstate of `f` will be deleted.
# The motivitation is that normally, an assignment (or function definition) will add that symbol to a list of 'hidden globals' - any future references to that symbol will be ignored. i.e. the _local definition hides a global_.
# In the case of functions, you can reference functions and variables that do not yet exist, and so they won't be in the list of hidden symbols when the function definition is analysed.
# Of course, our method will fail if a referenced function is defined both inside the cell **and** in another cell. However, this will lead to a MultipleDefinitionError before anything bad happens.
for (func, inner_symstate) in symstate.funcdefs
inner_symstate.references = setdiff(inner_symstate.references, keys(symstate.funcdefs))
inner_symstate.funccalls = setdiff(inner_symstate.funccalls, keys(symstate.funcdefs))
end
symstate
end
👀 Reading hidden code
1.6 ms
try_compute_symbolreferences (generic function with 1 method)
function try_compute_symbolreferences(ex::Any)::SymbolsState
try
compute_symbolreferences(ex)
catch e
@error "Expression explorer failed on: " ex
showerror(stderr, e, stacktrace(catch_backtrace()))
SymbolsState(references=Set{Symbol}([:fake_reference_to_prevent_it_from_looking_like_a_text_only_cell]))
end
end
👀 Reading hidden code
2.6 ms
UsingsImports
Base.@kwdef struct UsingsImports
usings::Set{Expr}=Set{Expr}()
imports::Set{Expr}=Set{Expr}()
end
👀 Reading hidden code
2.8 ms
compute_usings_imports!

Preallocated version of compute_usings_imports.

# Performance analysis: https://gist.github.com/fonsp/280f6e883f419fb3a59231b2b1b95cab
"Preallocated version of [`compute_usings_imports`](@ref)."
function compute_usings_imports!(out::UsingsImports, ex::Any)
if isa(ex, Expr)
if ex.head == :using
push!(out.usings, ex)
elseif ex.head == :import
push!(out.imports, ex)
elseif ex.head != :quote
for a in ex.args
compute_usings_imports!(out, a)
end
end
end
out
end
👀 Reading hidden code
1.4 ms
external_package_names

Given :(using Plots, Something.Else, .LocalModule), return Set([:Plots, :Something]).

"""
Given `:(using Plots, Something.Else, .LocalModule)`, return `Set([:Plots, :Something])`.
"""
function external_package_names(ex::Expr)::Set{Symbol}
@assert ex.head == :import || ex.head == :using
if Meta.isexpr(ex.args[1], :(:))
external_package_names(Expr(ex.head, ex.args[1].args[1]))
else
out = Set{Symbol}()
for a in ex.args
if Meta.isexpr(a, :(.))
if a.args[1] != :(.)
push!(out, a.args[1])
end
end
end
out
end
end
👀 Reading hidden code
16.0 ms
external_package_names (generic function with 2 methods)
function external_package_names(x::UsingsImports)::Set{Symbol}
union!(Set{Symbol}(), external_package_names.(x.usings)..., external_package_names.(x.imports)...)
end
👀 Reading hidden code
601 μs
compute_usings_imports

Get the sets of using Module and import Module subexpressions that are contained in this expression.

"Get the sets of `using Module` and `import Module` subexpressions that are contained in this expression."
compute_usings_imports(ex) = compute_usings_imports!(UsingsImports(), ex)
👀 Reading hidden code
607 μs
is_toplevel_expr

Return whether the expression is of the form Expr(:toplevel, LineNumberNode(..), any).

"Return whether the expression is of the form `Expr(:toplevel, LineNumberNode(..), any)`."
function is_toplevel_expr(ex::Expr)::Bool
(ex.head == :toplevel) && (length(ex.args) == 2) && (ex.args[1] isa LineNumberNode)
end
👀 Reading hidden code
903 μs
is_toplevel_expr (generic function with 2 methods)
is_toplevel_expr(::Any)::Bool = false
👀 Reading hidden code
374 μs
get_rootassignee

If the expression is a (simple) assignemnt at its root, return the assignee as Symbol, return nothing otherwise.

"If the expression is a (simple) assignemnt at its root, return the assignee as `Symbol`, return `nothing` otherwise."
function get_rootassignee(ex::Expr, recurse::Bool=true)::Union{Symbol,Nothing}
if is_toplevel_expr(ex) && recurse
get_rootassignee(ex.args[2], false)
elseif ex.head == :(=) && ex.args[1] isa Symbol
ex.args[1]
else
nothing
end
end
👀 Reading hidden code
1.4 ms
get_rootassignee (generic function with 4 methods)
get_rootassignee(ex::Any, recuse::Bool=true)::Union{Symbol,Nothing} = nothing
👀 Reading hidden code
650 μs
can_be_function_wrapped

Is this code simple enough that we can wrap it inside a function to boost performance? Look for PlutoRunner.Computer to learn more.

"Is this code simple enough that we can wrap it inside a function to boost performance? Look for [`PlutoRunner.Computer`](@ref) to learn more."
function can_be_function_wrapped(x::Expr)
if x.head === :global || # better safe than sorry
x.head === :using ||
x.head === :import ||
x.head === :module ||
x.head === :function ||
x.head === :macro ||
x.head === :macrocall || # we might want to get rid of this one, but that requires some work
x.head === :struct ||
x.head === :abstract ||
(x.head === :(=) && x.args[1] isa Expr && x.args[1].head === :call) || # f(x) = ...
(x.head === :call && (x.args[1] === :eval || x.args[1] === :include))
false
else
all(can_be_function_wrapped, x.args)
end

end
👀 Reading hidden code
1.8 ms
can_be_function_wrapped (generic function with 2 methods)
can_be_function_wrapped(x::Any) = true
👀 Reading hidden code
323 μs

Tests

👀 Reading hidden code
168 μs

Basics

👀 Reading hidden code
180 μs
testee(:a, [:a], [], [], [])
👀 Reading hidden code
96.1 ms
testee(:(1 + 1), [], [], [:+], [])
👀 Reading hidden code
673 ms
testee(:(sqrt(1)), [], [], [:sqrt], [])
👀 Reading hidden code
262 μs
testee(:(x = 3), [], [:x], [], [])
👀 Reading hidden code
97.8 ms
testee(:(x = x), [:x], [:x], [], [])
👀 Reading hidden code
5.7 ms
testee(:(x = 1 + y), [:y], [:x], [:+], [])
👀 Reading hidden code
3.0 ms
testee(:(x = +((a...))), [:a], [:x], [:+], [])
👀 Reading hidden code
3.1 ms
testee(:(1:3), [], [], [:(:)], [])
👀 Reading hidden code
269 μs

Bad code

👀 Reading hidden code
165 μs
👀 Reading hidden code
10.6 μs
testee(:(123 = x), [:x], [], [], [])
👀 Reading hidden code
2.9 ms
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
Keep calm, you got this!
@test_nowarn testee(:((a = b, c, d = 123,)), [:b], [], [], [], verbose=false)
👀 Reading hidden code
---
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
@test_nowarn testee(:((a = b, c[r] = 2, d = 123,)), [:b], [], [], [], verbose=false)
👀 Reading hidden code
---
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
this suckz 💣
@test_nowarn testee(:(function f(function g() end) end), [], [], [:+], [], verbose=false)
👀 Reading hidden code
---
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
computer bad, you GREAT!
@test_nowarn testee(:(function f() Base.sqrt(x::String) = 2; end), [], [], [:+], [], verbose=false)
👀 Reading hidden code
---
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
Probably not your fault!
@test_nowarn testee(:(function f() global g(x) = x; end), [], [], [], [], verbose=false)
👀 Reading hidden code
---

Lists and structs

👀 Reading hidden code
195 μs
testee(:(1:3), [], [], [:(:)], [])
👀 Reading hidden code
278 μs
testee(:(a[1:3, 4]), [:a], [], [:(:)], [])
👀 Reading hidden code
293 μs
testee(:(a[b]), [:a, :b], [], [], [])
👀 Reading hidden code
315 μs
testee(:([a[1:3, 4]; b[5]]), [:b, :a], [], [:(:)], [])
👀 Reading hidden code
305 μs
testee(:(a.someproperty), [:a], [], [], [])
👀 Reading hidden code
3.8 ms
testee(:([a..., b]), [:a, :b], [], [], [])
👀 Reading hidden code
247 μs
testee(:(struct a b c end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
365 ms
testee(:(let struct a b c end end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
32.1 ms
testee(:(module a f(x) = begin x end z = r end), [], [:a], [], [])
👀 Reading hidden code
264 μs

Types

👀 Reading hidden code
173 μs
testee(:(x::Foo = 3), [:Foo], [:x], [], [])
👀 Reading hidden code
93.5 ms
testee(:(x::Foo), [:x, :Foo], [], [], [])
👀 Reading hidden code
249 μs
testee(:((a::Foo, b::String = 1, "2")), [:Foo, :String], [:a, :b], [], [])
👀 Reading hidden code
77.1 ms
testee(:(Foo[]), [:Foo], [], [], [])
👀 Reading hidden code
256 μs
testee(:(x isa Foo), [:x, :Foo], [], [:isa], [])
👀 Reading hidden code
268 μs
testee(:(A{B} = B), [], [:A], [], [])
👀 Reading hidden code
18.9 ms
testee(:(A{T} = Union{T, Int}), [:Int, :Union], [:A], [], [])
👀 Reading hidden code
320 μs
testee(:(abstract type a end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
4.2 ms
testee(:(abstract type a <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
308 μs
testee(:(abstract type a <: b{C} end), [], [:a], [], [:a => ([:b, :C], [], [], [])])
👀 Reading hidden code
327 μs
testee(:(abstract type a{T} end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
326 μs
testee(:(abstract type a{T, S} end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
4.8 ms
testee(:(abstract type a{T} <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
319 μs
testee(:(abstract type a{T} <: b{T} end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
351 μs
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
¯\_(ツ)_/¯
@test_nowarn testee(macroexpand(Main, :(@enum a b c)), [], [], [], []; verbose=false)
👀 Reading hidden code
---
:(struct a
      #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#7e4cf861-f9f7-46b4-a7d2-36575386259c:1 =#
  end)
👀 Reading hidden code
124 μs
testee(e, [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
45.6 ms
testee(:(struct a <: b c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
12.7 ms
testee(:(struct a{T, S} c::T d::Foo end), [], [:a], [], [:a => ([:Foo], [], [], [])])
👀 Reading hidden code
416 μs
testee(:(struct a{T} <: b c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
421 μs
testee(:(struct a{T} <: b{T} c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
424 μs
testee(:(struct a c a(x = y) = begin new(x, z) end end), [], [:a], [], [:a => ([:y, :z], [], [:new], [])])
👀 Reading hidden code
4.0 ms
👀 Reading hidden code
10.1 μs

Assignment operator & modifiers

👀 Reading hidden code
200 μs
testee(:(a = a), [:a], [:a], [], [])
👀 Reading hidden code
244 μs
testee(:(a = a + 1), [:a], [:a], [:+], [])
👀 Reading hidden code
293 μs
testee(:(x = (a = a + 1)), [:a], [:a, :x], [:+], [])
👀 Reading hidden code
328 μs
testee(:(const a = b), [:b], [:a], [], [])
👀 Reading hidden code
274 μs
testee(:(f(x) = begin x end), [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
73.0 ms
testee(:(a[b, c, :] = d), [:a, :b, :c, :d, :(:)], [], [], [])
👀 Reading hidden code
4.8 ms
testee(:(a.b = c), [:a, :c], [], [], [])
👀 Reading hidden code
276 μs
testee(:(f(a, b = c, d = e; f = g)), [:a, :c, :e, :g], [], [:f], [])
👀 Reading hidden code
326 μs
testee(:(a += 1), [:a], [:a], [:+], [])
👀 Reading hidden code
286 μs
testee(:(a >>>= 1), [:a], [:a], [:>>>], [])
👀 Reading hidden code
323 μs
testee(:(a ⊻= 1), [:a], [:a], [:⊻], [])
👀 Reading hidden code
295 μs
testee(:(a[1] += 1), [:a], [], [:+], [])
👀 Reading hidden code
318 μs
testee(:(x = let a = 1 a += b end), [:b], [:x], [:+], [])
👀 Reading hidden code
352 μs
testee(:(_ = a + 1), [:a], [], [:+], [])
👀 Reading hidden code
341 μs
testee(:(a = _ + 1), [], [:a], [:+], [])
👀 Reading hidden code
296 μs

Tuples

👀 Reading hidden code
167 μs
testee(:((a, b)), [:a, :b], [], [], [])
👀 Reading hidden code
254 μs
testee(:((a = b, c = 2, d = 123)), [:b], [], [], [])
👀 Reading hidden code
41.3 ms
testee(:((a = b,)), [:b], [], [], [])
👀 Reading hidden code
271 μs
testee(:((a, b = 1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
340 μs
testee(:((a, _, c, __ = 1, 2, 3, _d)), [:_d], [:a, :c], [], [])
👀 Reading hidden code
13.3 ms
testee(:(const (a, b) = (1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
351 μs
testee(:(((a, b) = 1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
6.1 ms
testee(:((a = b, c)), [:b, :c], [:a], [], [])
👀 Reading hidden code
306 μs
testee(:((a, b = c)), [:c], [:a, :b], [], [])
👀 Reading hidden code
361 μs
testee(:(a = (b, c)), [:b, :c], [:a], [], [])
👀 Reading hidden code
278 μs
testee(:((a, (b, c) = [e, [f, g]])), [:e, :f, :g], [:a, :b, :c], [], [])
👀 Reading hidden code
382 μs
testee(:(((x, y), a, (b, c) = z, e, (f, g))), [:z, :e, :f, :g], [:x, :y, :a, :b, :c], [], [])
👀 Reading hidden code
431 μs
testee(:(((x[i], y.r), a, (b, c) = z, e, (f, g))), [:x, :i, :y, :z, :e, :f, :g], [:a, :b, :c], [], [])
👀 Reading hidden code
3.1 ms
testee(:((a[i], b.r) = (c.d, 2)), [:a, :b, :i, :c], [], [], [])
👀 Reading hidden code
401 μs

Broadcasting

👀 Reading hidden code
176 μs
testee(:(a .= b), [:b, :a], [], [], [])
👀 Reading hidden code
242 μs
testee(:(a .+= b), [:b, :a], [], [:+], [])
👀 Reading hidden code
268 μs
testee(:(a[i] .+= b), [:b, :a, :i], [], [:+], [])
👀 Reading hidden code
296 μs
testee(:(a .+ b ./ sqrt.(c, d)), [:a, :b, :c, :d], [], [:+, :/, :sqrt], [])
👀 Reading hidden code
349 μs

for & while

👀 Reading hidden code
182 μs
testee(:(for k = 1:n k + s end), [:n, :s], [], [:+, :(:)], [])
👀 Reading hidden code
368 μs
testee(:(for k = 1:2, r = 3:4 global z = k + r end), [], [:z], [:+, :(:)], [])
👀 Reading hidden code
386 μs
testee(:(while k < 2 r = w global z = k + r end), [:k, :w], [:z], [:+, :<], [])
👀 Reading hidden code
367 μs

try & catch

👀 Reading hidden code
178 μs
testee(:(try a = b + 1 catch end), [:b], [], [:+], [])
👀 Reading hidden code
11.3 ms
testee(:(try a() catch e e end), [], [], [:a], [])
👀 Reading hidden code
14.9 ms
testee(:(try a() catch e end), [:e], [], [:a], [])
👀 Reading hidden code
335 μs
testee(:(try a + 1 catch a a end), [:a], [], [:+], [])
👀 Reading hidden code
332 μs
testee(:(try 1 catch e e finally a end), [:a], [], [], [])
👀 Reading hidden code
8.2 ms
testee(:(try 1 finally a end), [:a], [], [], [])
👀 Reading hidden code
316 μs

Comprehensions

👀 Reading hidden code
166 μs
testee(:([sqrt(s) for s = 1:n]), [:n], [], [:sqrt, :(:)], [])
👀 Reading hidden code
342 μs
testee(:([sqrt(s + r) for s = 1:n, r = k]), [:n, :k], [], [:sqrt, :(:), :+], [])
👀 Reading hidden code
369 μs
testee(:([s + j + r + m for s = 1:3 for j = 4:5 for (r, l) = [(1, 2)]]), [:m], [], [:+, :(:)], [])
👀 Reading hidden code
476 μs
testee(:([a for a = b if a != 2]), [:b], [], [:!=], [])
👀 Reading hidden code
41.8 ms
testee(:([a for a = f() if g(a)]), [], [], [:f, :g], [])
👀 Reading hidden code
391 μs
testee(:([c(a) for a = f() if g(a)]), [], [], [:c, :f, :g], [])
👀 Reading hidden code
349 μs
testee(:([a for a = a]), [:a], [], [], [])
👀 Reading hidden code
317 μs
testee(:(for a = a a end), [:a], [], [], [])
👀 Reading hidden code
292 μs
testee(:(let a = a a end), [:a], [], [], [])
👀 Reading hidden code
290 μs
testee(:(let a = a end), [:a], [], [], [])
👀 Reading hidden code
267 μs
testee(:(let a = b end), [:b], [], [], [])
👀 Reading hidden code
287 μs
testee(:(a = a), [:a], [:a], [], [])
👀 Reading hidden code
240 μs
testee(:(a = [a for a = a]), [:a], [:a], [], [])
👀 Reading hidden code
291 μs

Multiple expressions

👀 Reading hidden code
165 μs
testee(:(x = let r = 1 r + r end), [], [:x], [:+], [])
👀 Reading hidden code
357 μs
testee(quote let r = 1 r + r end r = 2 end, [], [:r], [:+], [])
👀 Reading hidden code
394 μs
testee(quote k = 2 123 end, [], [:k], [], [])
👀 Reading hidden code
297 μs
testee(quote a = 1 b = a + 1 end, [], [:a, :b], [:+], [])
👀 Reading hidden code
331 μs
testee(Meta.parse("a = 1; b = a + 1"), [], [:a, :b], [:+], [])
👀 Reading hidden code
3.4 ms
testee(:(a = (b = 1)), [], [:a, :b], [], [])
👀 Reading hidden code
253 μs
testee(:(let k = 2 123 end), [], [], [], [])
👀 Reading hidden code
307 μs
testee(:(let k() = begin 2 end end), [], [], [], [])
👀 Reading hidden code
339 μs

Functions

👀 Reading hidden code
166 μs
testee(:(function g() r = 2 r end), [], [], [], [:g => ([], [], [], [])])
👀 Reading hidden code
357 μs
testee(:(function g end), [], [], [], [:g => ([], [], [], [])])
👀 Reading hidden code
295 μs
testee(:(function f() g(x) = begin x end end), [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
387 μs
testee(:(function f(x, y = 1; r, s = 3 + 3) r + s + x * y * z end), [], [], [], [:f => ([:z], [], [:+, :*], [])])
👀 Reading hidden code
47.8 ms
testee(:(function f(x) x * y * z end), [], [], [], [:f => ([:y, :z], [], [:*], [])])
👀 Reading hidden code
420 μs
testee(:(function f(x) x = x / 3 x end), [], [], [], [:f => ([], [], [:/], [])])
👀 Reading hidden code
435 μs
testee(quote function f(x) a end function f(x, y) b end end, [], [], [], [:f => ([:a, :b], [], [], [])])
👀 Reading hidden code
460 μs
testee(:(function f(x, args...; kwargs...) return [x, y, args..., kwargs...] end), [], [], [], [:f => ([:y], [], [], [])])
👀 Reading hidden code
433 μs
testee(:(function f(x; y = x) y + x end), [], [], [], [:f => ([], [], [:+], [])])
👀 Reading hidden code
25.3 ms
testee(:(function (A::MyType)(x; y = x) y + x end), [], [], [], [:MyType => ([], [], [:+], [])])
👀 Reading hidden code
412 μs
testee(:(f(x, y = a + 1) = begin x * y * z end), [], [], [], [:f => ([:z, :a], [], [:*, :+], [])])
👀 Reading hidden code
415 μs
testee(quote f() = begin 1 end f end, [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
340 μs
testee(quote f() = begin 1 end f() end, [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
368 μs
testee(quote f(x) = begin global a = √b end f(x, y) = begin global c = -d end end, [], [], [], [:f => ([:b, :d], [:a, :c], [:√, :-], [])])
👀 Reading hidden code
513 μs
testee(:(Base.show() = begin 0 end), [:Base], [], [], [[:Base, :show] => ([], [], [], [])])
👀 Reading hidden code
39.4 ms
testee(:((begin x p end->begin f(x + p) end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
👀 Reading hidden code
23.8 ms
testee(:((begin x p end->begin f(x + p) end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
👀 Reading hidden code
442 μs
testee(:(minimum(x) do (a, b) a + b end), [:x], [], [:minimum], [:anon => ([], [], [:+], [])])
👀 Reading hidden code
431 μs
testee(:(f = (x->begin x * y end)), [], [:f], [], [:anon => ([:y], [], [:*], [])])
👀 Reading hidden code
412 μs
testee(:(f = ((x, y)->begin x * y end)), [], [:f], [], [:anon => ([], [], [:*], [])])
👀 Reading hidden code
418 μs
testee(:(f = ((x, y = a + 1)->begin x * y end)), [], [:f], [], [:anon => ([:a], [], [:*, :+], [])])
👀 Reading hidden code
464 μs
testee(:(((((a, b), c), (d, e))->begin a * b * c * d * e * f end)), [], [], [], [:anon => ([:f], [], [:*], [])])
👀 Reading hidden code
437 μs
testee(:((a...->begin f(a...) end)), [], [], [], [:anon => ([], [], [:f], [])])
👀 Reading hidden code
404 μs
testee(:(f = (args...->begin [args..., y] end)), [], [:f], [], [:anon => ([:y], [], [], [])])
👀 Reading hidden code
399 μs
testee(:(f = ((x, args...; kwargs...)->begin [x, y, args..., kwargs...] end)), [], [:f], [], [:anon => ([:y], [], [], [])])
👀 Reading hidden code
451 μs
testee(:(f = function (a, b) a + b * n end), [:n], [:f], [:+, :*], [])
👀 Reading hidden code
392 μs
testee(:(f = function () a + b end), [:a, :b], [:f], [:+], [])
👀 Reading hidden code
366 μs
testee(:(func(a)), [:a], [], [:func], [])
👀 Reading hidden code
271 μs
testee(:(func(a; b = c)), [:a, :c], [], [:func], [])
👀 Reading hidden code
305 μs
testee(:(func(a, b = c)), [:a, :c], [], [:func], [])
👀 Reading hidden code
288 μs
testee(:(√b), [:b], [], [:√], [])
👀 Reading hidden code
264 μs
testee(:((funcs[i])(b)), [:funcs, :i, :b], [], [], [])
👀 Reading hidden code
281 μs
testee(:((f(a))(b)), [:a, :b], [], [:f], [])
👀 Reading hidden code
9.8 ms
testee(:((f(a)).b()), [:a], [], [:f], [])
👀 Reading hidden code
27.8 ms
testee(:(a.b(c)), [:a, :c], [], [[:a, :b]], [])
👀 Reading hidden code
14.7 ms
testee(:(a.b.c(d)), [:a, :d], [], [[:a, :b, :c]], [])
👀 Reading hidden code
7.5 ms
testee(:((a.b(c))(d)), [:a, :c, :d], [], [[:a, :b]], [])
👀 Reading hidden code
350 μs
testee(:((a.b(c)).d(e)), [:a, :c, :e], [], [[:a, :b]], [])
👀 Reading hidden code
364 μs
testee(:((a.b[c]).d(e)), [:a, :c, :e], [], [], [])
👀 Reading hidden code
297 μs

Functions & types

👀 Reading hidden code
173 μs
testee(:(function f(y::Int64 = a)::String string(y) end), [], [], [], [:f => ([:String, :Int64, :a], [], [:string], [])])
👀 Reading hidden code
419 μs
testee(quote f(a::A)::C = begin a.a end end, [], [], [], [:f => ([:A, :C], [], [], [])])
👀 Reading hidden code
401 μs
testee(:(function f(x::T; k = 1) where T return x + 1 end), [], [], [], [:f => ([], [], [:+], [])])
👀 Reading hidden code
60.7 ms
testee(:(function f(x::T; k = 1) where {T, S <: R} return x + 1 end), [], [], [], [:f => ([:R], [], [:+], [])])
👀 Reading hidden code
484 μs
testee(:(f(x)::String = begin x end), [], [], [], [:f => ([:String], [], [], [])])
👀 Reading hidden code
402 μs
testee(:(@MIME_str()), [], [], [Symbol("@MIME_str")], [])
👀 Reading hidden code
184 ms
testee(:(function f(::@MIME_str()) 1 end), [], [], [], [:f => ([], [], [Symbol("@MIME_str")], [])])
👀 Reading hidden code
13.7 ms
testee(:((a(a::AbstractArray{T}) where T) = begin 5 end), [], [], [], [:a => ([:AbstractArray], [], [], [])])
👀 Reading hidden code
428 μs
testee(:((a(a::AbstractArray{T, R}) where {T, S}) = begin a + b end), [], [], [], [:a => ([:AbstractArray, :b, :R], [], [:+], [])])
👀 Reading hidden code
428 μs
testee(:(f(::A) = begin 1 end), [], [], [], [:f => ([:A], [], [], [])])
👀 Reading hidden code
359 μs
testee(:(f(::A, ::B) = begin 1 end), [], [], [], [:f => ([:A, :B], [], [], [])])
👀 Reading hidden code
365 μs
testee(:(f(a::A, ::B, c::C...) = begin a + c end), [], [], [], [:f => ([:A, :B, :C], [], [:+], [])])
👀 Reading hidden code
438 μs
testee(:((obj::MyType)(x, y) = begin x + z end), [], [], [], [:MyType => ([:z], [], [:+], [])])
👀 Reading hidden code
408 μs
testee(:((obj::MyType)() = begin 1 end), [], [], [], [:MyType => ([], [], [], [])])
👀 Reading hidden code
347 μs
testee(:((obj::MyType)(x, args...; kwargs...) = begin [x, y, args..., kwargs...] end), [], [], [], [:MyType => ([:y], [], [], [])])
👀 Reading hidden code
427 μs
testee(:(function (obj::MyType)(x, y) x + z end), [], [], [], [:MyType => ([:z], [], [:+], [])])
👀 Reading hidden code
406 μs
testee(quote struct MyType x::String end (obj::MyType)(y) = begin obj.x + y end end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
👀 Reading hidden code
553 μs
testee(quote struct MyType x::String end function (obj::MyType)(y) obj.x + y end end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
👀 Reading hidden code
548 μs
testee(:((::MyType)(x, y) = begin x + y end), [], [], [], [:MyType => ([], [], [:+], [])])
👀 Reading hidden code
430 μs

Scope modifiers

👀 Reading hidden code
168 μs
testee(:(let global (a, b) = (1, 2) end), [], [:a, :b], [], [])
👀 Reading hidden code
378 μs
Error message

UndefVarError: @test_broken not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
@test_broken testee(:(let global a = b = 1 end), [], [:a], [], []; verbose=false)
👀 Reading hidden code
---
testee(:(let global k = 3 end), [], [:k], [], [])
👀 Reading hidden code
317 μs
Error message

UndefVarError: @test_broken not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
You got this!
@test_broken testee(:(let global k = r end), [], [:k], [], []; verbose=false)
👀 Reading hidden code
---
testee(:(let global k = 3 k end), [], [:k], [], [])
👀 Reading hidden code
310 μs
testee(:(let global k += 3 end), [:k], [:k], [:+], [])
👀 Reading hidden code
332 μs
testee(:(let global k k = 4 end), [], [:k], [], [])
👀 Reading hidden code
301 μs
testee(:(let global k b = 5 end), [], [], [], [])
👀 Reading hidden code
302 μs
testee(:(let a = 1, b = 2 show(a + b) end), [], [], [:show, :+], [])
👀 Reading hidden code
360 μs
testee(quote local (a, b) = (1, 2) end, [], [], [], [])
👀 Reading hidden code
369 μs
testee(quote local a = (b = 1) end, [], [:b], [], [])
👀 Reading hidden code
283 μs
testee(quote local k = 3 end, [], [], [], [])
👀 Reading hidden code
275 μs
testee(quote local k = r end, [:r], [], [], [])
👀 Reading hidden code
277 μs
testee(quote local k = 3 k b = 4 end, [], [:b], [], [])
👀 Reading hidden code
306 μs
testee(quote local k += 3 end, [], [], [:+], [])
👀 Reading hidden code
304 μs
testee(quote local k k = 4 end, [], [], [], [])
👀 Reading hidden code
274 μs
testee(quote local k b = 5 end, [], [:b], [], [])
👀 Reading hidden code
306 μs
testee(quote local r[1] = 5 end, [:r], [], [], [])
👀 Reading hidden code
305 μs
Error message

UndefVarError: @test_broken not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
Computers are hard!
@test_broken testee(:(begin begin local a = 2 end; a end), [:a], [], [], []; verbose=false)
👀 Reading hidden code
---
testee(:(function f(x) global k = x end), [], [], [], [:f => ([], [:k], [], [])])
👀 Reading hidden code
396 μs
testee(:((begin x = 1 end, y)), [:y], [:x], [], [])
👀 Reading hidden code
7.5 ms
testee(:(x = let global a += 1 end), [:a], [:x, :a], [:+], [])
👀 Reading hidden code
376 μs

import & using

👀 Reading hidden code
184 μs
testee(:(using Plots), [], [:Plots], [], [])
👀 Reading hidden code
42.5 ms
testee(:(using Plots.ExpressionExplorer), [], [:ExpressionExplorer], [], [])
👀 Reading hidden code
279 μs
testee(:(using JSON, UUIDs), [], [:JSON, :UUIDs], [], [])
👀 Reading hidden code
263 μs
testee(:(import Pluto), [], [:Pluto], [], [])
👀 Reading hidden code
249 μs
testee(:(import Pluto: wow, wowie), [], [:wow, :wowie], [], [])
👀 Reading hidden code
7.2 ms
testee(:(import Pluto.ExpressionExplorer.wow, Plutowie), [], [:wow, :Plutowie], [], [])
👀 Reading hidden code
280 μs
testee(:(import .Pluto: wow), [], [:wow], [], [])
👀 Reading hidden code
238 μs
testee(:(import ..Pluto: wow), [], [:wow], [], [])
👀 Reading hidden code
242 μs
testee(:(let import Pluto.wow, Dates end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
317 μs
testee(:(while false import Pluto.wow, Dates end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
8.9 ms
testee(:(try using Pluto.wow, Dates catch end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
316 μs
testee(:(module A import B end), [], [:A], [], [])
👀 Reading hidden code
240 μs

Foreign macros

parameterizedfunctions

👀 Reading hidden code
213 μs
testee(quote f = @ode_def(begin dx = a * x - b * x * y dy = -c * y + d * x * y end, a, b, c, d) end, [], [:f, :LotkaVolterra], [Symbol("@ode_def")], [])
👀 Reading hidden code
1.3 ms
testee(quote f = @ode_def(a, b, c, d) end, [], [:f], [Symbol("@ode_def")], [])
👀 Reading hidden code
1.2 ms

Flux

👀 Reading hidden code
176 μs
testee(:(@functor()), [], [:Asdf], [Symbol("@functor")], [])
👀 Reading hidden code
1.2 ms

Symbolics.jl

👀 Reading hidden code
177 μs
testee(:(@variables(b, c)), [], [:a, :b, :c], [Symbol("@variables")], [])
👀 Reading hidden code
142 ms
testee(:(@variables()), [], [:a, :b, :c], [Symbol("@variables")], [])
👀 Reading hidden code
1.3 ms
testee(:(@variables(b[1:2], c(t), d(..))), [], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
👀 Reading hidden code
14.2 ms
testee(:(@variables(b[1:x], (c[1:10])(t), d(..))), [:x], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
👀 Reading hidden code
4.9 ms
testee(:(@variables()), [:x], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
👀 Reading hidden code
1.4 ms
Error message

UndefVarError: @test_nowarn not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
uhmmmmmm??!
@test_nowarn testee(:(@variables(m, begin
x
y[i=1:2] >= i, (start = i, base_name = "Y_$i")
z, Bin
end)), [:m, :Bin], [:x, :y, :z], [Symbol("@variables")], [], verbose=false)
👀 Reading hidden code
---

JuMP

👀 Reading hidden code
166 μs
👀 Reading hidden code
10.3 μs
👀 Reading hidden code
11.7 μs
👀 Reading hidden code
11.3 μs
👀 Reading hidden code
11.7 μs
👀 Reading hidden code
10.9 μs
👀 Reading hidden code
11.4 μs
👀 Reading hidden code
11.8 μs

Macros

👀 Reading hidden code
223 μs
testee(:(@time()), [], [:a], [Symbol("@time")], [])
👀 Reading hidden code
318 μs
testee(:(@f(x)), [:x, :z], [], [Symbol("@f")], [])
👀 Reading hidden code
30.5 ms
testee(:(@f(y = z)), [:x, :z], [], [Symbol("@f")], [])
👀 Reading hidden code
26.9 ms
testee(:(Base.@time()), [:Base], [:a], [[:Base, Symbol("@time")]], [])
👀 Reading hidden code
404 μs
testee(:(@gensym(b, c)), [], [:a, :b, :c], [:gensym, Symbol("@gensym")], [])
👀 Reading hidden code
18.0 ms
testee(:(Base.@gensym(b, c)), [:Base], [:a, :b, :c], [:gensym, [:Base, Symbol("@gensym")]], [])
👀 Reading hidden code
45.4 ms
testee(:(Base.@kwdef()), [:Base], [:A], [[:Base, Symbol("@kwdef")], [:Base, Symbol("@__doc__")]], [:A => ([:Int, :two], [], [], [])])
👀 Reading hidden code
1.5 ms
testee(quote Core.@doc f(x) = begin x end end, [], [], [Symbol("@doc")], [:f => ([], [], [], [])])
👀 Reading hidden code
86.8 ms
testee(:(@bind(b)), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], Symbol("@bind")], [])
👀 Reading hidden code
❔
FAILED TEST
#= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#a0a1dc6e-81ff-419e-a650-3d37c0c2891b:1 =# @bind a b

Expr
  head: Symbol macrocall
  args: Array{Any}((4,))
    1: Symbol @bind
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#a0a1dc6e-81ff-419e-a650-3d37c0c2891b
    3: Symbol a
    4: Symbol b

expected = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:Base, :get], [:PlutoRunner, :Bond]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())
resulted = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:PlutoRunner, :create_bond], [:Base, :get]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())

2.5 s
testee(:(PlutoRunner.@bind(b)), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], [:PlutoRunner, Symbol("@bind")]], [])
👀 Reading hidden code
❔
FAILED TEST
#= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#8a4269da-65e9-442d-affe-113fa2d33924:1 =# PlutoRunner.@bind a b

Expr
  head: Symbol macrocall
  args: Array{Any}((4,))
    1: Expr
      head: Symbol .
      args: Array{Any}((2,))
        1: Symbol PlutoRunner
        2: QuoteNode
          value: Symbol @bind
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#8a4269da-65e9-442d-affe-113fa2d33924
    3: Symbol a
    4: Symbol b

expected = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[:Core, :applicable], [:Base, :get], [:PlutoRunner, :Bond], [:PlutoRunner, Symbol("@bind")]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())
resulted = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[:Core, :applicable], [:PlutoRunner, :create_bond], [:Base, :get], [:PlutoRunner, Symbol("@bind")]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())

6.4 ms
Error message

UndefVarError: @test_broken not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. macroexpand
@test_broken testee(:(Main.PlutoRunner.@bind a b), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], [:PlutoRunner, Symbol("@bind")]], [], verbose=false)
👀 Reading hidden code
---
testee(:(let @bind(b) end), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], Symbol("@bind")], [])
👀 Reading hidden code
❔
FAILED TEST
let #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#89d6f27e-5ec9-4a32-9351-dec8e614e704:1 =# @bind(a, b)
end

Expr
  head: Symbol let
  args: Array{Any}((2,))
    1: Expr
      head: Symbol block
      args: Array{Any}((1,))
        1: Expr
          head: Symbol macrocall
          args: Array{Any}((4,))
            1: Symbol @bind
            2: LineNumberNode
              line: Int64 1
              file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#89d6f27e-5ec9-4a32-9351-dec8e614e704
            3: Symbol a
            4: Symbol b
    2: Expr
      head: Symbol block
      args: Array{Any}((0,))

expected = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:Base, :get], [:PlutoRunner, :Bond]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())
resulted = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:PlutoRunner, :create_bond], [:Base, :get]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())

48.0 ms
testee(:(@asdf(b = x2, c = x3)), [:x1, :x2, :x3], [:a], [Symbol("@asdf")], [])
👀 Reading hidden code
1.2 ms
testee(:(@einsum()), [:x, :y, :Float64], [:a], [[Symbol("@einsum")], [:*]], [])
👀 Reading hidden code
213 ms
testee(:(@tullio(init = z)), [:x, :k, :z], [:a], [[Symbol("@tullio")], [:f], [:*], [:+]], [])
👀 Reading hidden code
60.6 ms
testee(:(Pack.@asdf()), [:x, :y, :k, :Pack, :Float64], [:a], [[:Pack, Symbol("@asdf")], [:/], [:log]], [])
👀 Reading hidden code
569 μs
Error message

Failed to show value:

BoundsError: attempt to access 2-element Vector{Any} at index [3]

Stack trace

Here is what happened, the most recent locations are first:

  1. getindex
  2. show_unquoted(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64)
  3. show_unquoted(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64)
  4. show_list(io::IOContext{IOBuffer}, items::Vector{Any}, sep::String, indent::Int64, prec::Int64, quote_level::Int64, enclose_operators::Bool, kw::Bool)
  5. show_enclosed_list
  6. show_call(io::IOContext{IOBuffer}, head::Symbol, func::Symbol, func_args::Vector{Any}, indent::Int64, quote_level::Int64, kw::Bool)
  7. show_unquoted(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64)
  8. show_unquoted
  9. print(io::IOBuffer, ex::Expr)
  10. print_to_string(::String, ::Vararg{Any})
    from io.jl:144
  11. string(::String, ::Expr, ::Vararg{Any})
    from io.jl:185
  12. show(io::IOContext{IOBuffer}, mime::MIME{Symbol("text/html")}, value::Main.workspace#2.Pass)
    function Base.show(io::IO, mime::MIME"text/html", value::Pass)	show(io, mime, HTML("""		<div			style="
👀 Reading hidden code
33.4 ms
testee(:(@md_str()), [:b, :PlutoRunner, :Base, :Core], [:a], [:getindex, [:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], Symbol("@md_str"), Symbol("@bind")], [])
👀 Reading hidden code
❔
FAILED TEST
md"hey $(@bind a b) $(a)"

Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @md_str
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#8526af13-50da-4830-9631-827a2fad8ce3
    3: String "hey \$(@bind a b) \$(a)"

expected = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:Base, :get], [:getindex], [Symbol("@md_str")], [:PlutoRunner, :Bond]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())
resulted = Main.workspace#2.SymbolsState(Set([:b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:PlutoRunner, :create_bond], [:Base, :get], [:getindex], [Symbol("@md_str")]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())

135 ms
testee(:(@md_str()), [:b, :a, :PlutoRunner, :Base, :Core], [:a], [:getindex, [:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], Symbol("@md_str"), Symbol("@bind")], [])
👀 Reading hidden code
❔
FAILED TEST
md"hey $(a) $(@bind a b)"

Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @md_str
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#f9ea1f06-75a9-4cfa-b12d-ead56b94cb20
    3: String "hey \$(a) \$(@bind a b)"

expected = Main.workspace#2.SymbolsState(Set([:a, :b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:Base, :get], [:getindex], [Symbol("@md_str")], [:PlutoRunner, :Bond]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())
resulted = Main.workspace#2.SymbolsState(Set([:a, :b, :PlutoRunner, :Base, :Core]), Set([:a]), Set([[Symbol("@bind")], [:Core, :applicable], [:PlutoRunner, :create_bond], [:Base, :get], [:getindex], [Symbol("@md_str")]]), Dict{Main.workspace#2.FunctionNameSignaturePair, Main.workspace#2.SymbolsState}())

2.9 ms
testee(:(@html_str()), [], [], [Symbol("@html_str")], [])
👀 Reading hidden code
321 μs
testee(:(@md_str()), [:c], [:b], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
579 μs
testee(:(@md_str()), [:r], [], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
15.1 ms
testee(:(@md_str()), [], [], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
446 μs
testee(:(macro a() end), [], [], [], [Symbol("@a") => ([], [], [], [])])
👀 Reading hidden code
9.4 ms
testee(:(macro a(b::Int) b end), [], [], [], [Symbol("@a") => ([:Int], [], [], [])])
👀 Reading hidden code
417 μs
testee(:(macro a(b::Int = c) end), [], [], [], [Symbol("@a") => ([:Int, :c], [], [], [])])
👀 Reading hidden code
405 μs
testee(:(macro a() b = c return b end), [], [], [], [Symbol("@a") => ([:c], [], [], [])])
👀 Reading hidden code
429 μs

String interpolation & expressions

👀 Reading hidden code
186 μs
testee(:("a $(b)"), [:b], [], [], [])
👀 Reading hidden code
244 μs
testee(:("a $(b = c)"), [:c], [:b], [], [])
👀 Reading hidden code
273 μs
testee(:(ex = :yayo), [], [:ex], [], [])
👀 Reading hidden code
3.4 ms
testee(:(ex = :(yayo + $r)), [], [:ex], [], [])
👀 Reading hidden code
272 μs
👀 Reading hidden code
10.9 μs

Extracting using and import

👀 Reading hidden code
210 μs
quote
    #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:2 =#
    using A
    #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:3 =#
    import B
    #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:4 =#
    if x
        #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:5 =#
        using .C: r
        #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:6 =#
        import ..D.E: f, g
    else
        #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:8 =#
        import H.I, J, K.L
    end
    #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:11 =#
    $(Expr(:quote, quote
    #= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#ea51ec5d-ae1a-41bd-934b-93b77b6c6e9b:12 =#
    using Nonono
end))
end
👀 Reading hidden code
273 μs
👀 Reading hidden code
15.0 ms
using_test_result.usings == Set{Expr}([:(using A), :(using .C: r)])
👀 Reading hidden code
62.9 ms
using_test_result.imports == Set{Expr}([:(import B), :(import ..D.E: f, g), :(import H.I, J, K.L)])
👀 Reading hidden code
244 μs
external_package_names(using_test_result) == Set{Symbol}([:A, :B, :H, :J, :K])
👀 Reading hidden code
15.3 ms
external_package_names(:(using Plots, Something.Else, .LocalModule)) == Set([:Plots, :Something])
👀 Reading hidden code
234 μs
external_package_names(:(import Plots.A: b, c)) == Set([:Plots])
👀 Reading hidden code
209 μs

Appendix

👀 Reading hidden code
179 μs
testee

Calls ExpressionExplorer.compute_symbolreferences on the given expr and test the found SymbolsState against a given one, with convient syntax.

Example

julia> @test testee(:(
    begin
        a = b + 1
        f(x) = x / z
    end),
    [:b, :+], # 1st: expected references
    [:a, :f], # 2nd: expected definitions
    [:+],     # 3rd: expected function calls
    [
        :f => ([:z, :/], [], [:/], [])
    ])        # 4th: expected function definitions, with inner symstate using the same syntax
true
👀 Reading hidden code
25.7 ms
easy_symstate (generic function with 1 method)
👀 Reading hidden code
2.1 ms

Visual testing

👀 Reading hidden code
174 μs
abstract type TestResult end
👀 Reading hidden code
349 μs
Any
const Code = Any
👀 Reading hidden code
104 μs
struct Pass <: TestResult
expr::Code
end
👀 Reading hidden code
1.2 ms
abstract type Fail <: TestResult end
👀 Reading hidden code
333 μs
struct Wrong <: Fail
expr::Code
result
end
👀 Reading hidden code
1.2 ms
struct Error <: Fail
expr::Code
error
end
👀 Reading hidden code
1.7 ms
function Base.show(io::IO, mime::MIME"text/html", value::Pass)
show(io, mime, HTML("""
<div
style="
display: flex;
flex-direction: row;
align-items: center;
/*background-color: rgb(208, 255, 209)*/
"
>
<div
style="
width: 12px;
height: 12px;
border-radius: 50%;
background-color: green;
"
></div>
<div style="min-width: 12px"></div>
<code
class="language-julia"
style="
flex: 1;
background-color: transparent;
filter: grayscale(1) brightness(0.8);
"
>$(remove_linenums(value.expr))</code>
</div>
"""))
end
👀 Reading hidden code
533 μs
function Base.show(io::IO, mime::MIME"text/html", value::Wrong)
show(io, mime, HTML("""
<div
style="
display: flex;
flex-direction: row;
align-items: center;
/*background-color: rgb(208, 255, 209)*/
"
>
<div
style="
width: 12px;
height: 12px;
border-radius: 50%;
background-color: red;
"
></div>
<div style="min-width: 12px"></div>
<code
class="language-julia"
style="
flex: 1;
background-color: transparent;
filter: grayscale(1) brightness(0.8);
"
>$(remove_linenums(value.expr))</code>
</div>
"""))
end
👀 Reading hidden code
503 μs
function Base.show(io::IO, mime::MIME"text/html", value::Error)
show(io, mime, HTML("""
<div
style="
display: flex;
flex-direction: row;
align-items: center;
/*background-color: rgb(208, 255, 209)*/
"
>
<div
style="
width: 12px;
height: 12px;
border-radius: 50%;
background-color: red;
"
></div>
<div style="width: 12px"></div>
<div>
<code
class="language-julia"
style="
background-color: transparent;
filter: grayscale(1) brightness(0.8);
"
>$(remove_linenums(value.expr))</code>
<div style="
font-family: monospace;
font-size: 12px;
color: red;
padding-left: 8px;
">Error: $(sprint(showerror, value.error))</div>
</div>
</div>
"""))
end
👀 Reading hidden code
3.3 ms
# Only define this in Pluto - assume we are `using Test` otherwise
begin
@skip_as_script macro test(expr)
quote
expr_raw = $(expr |> QuoteNode)
try
result = $(esc(expr))
if result == true
Pass(expr_raw)
else
Wrong(expr_raw, result)
end
catch e
Error(expr_raw, e)
end
# Base.@locals()
end
end
# Do nothing inside pluto (so we don't need to have Test as dependency)
# test/Diffing is `using Test` before including this file
@only_as_script ((@isdefined Test) ? nothing : macro test(expr) quote nothing end end)
end
👀 Reading hidden code
4.7 ms
@test2 (macro with 1 method)
macro test2(expr)
quote nothing end
end
👀 Reading hidden code
399 μs
notebook1 == deepcopy(notebook1)
Error: UndefVarError: notebook1 not defined
@skip_as_script @test notebook1 == deepcopy(notebook1)
👀 Reading hidden code
6.1 ms
remove_linenums (generic function with 1 method)
remove_linenums(e::Expr) = Expr(e.head, (remove_linenums(x) for x in e.args if !(x isa LineNumberNode))...)
👀 Reading hidden code
1.3 ms
remove_linenums (generic function with 2 methods)
remove_linenums(x) = x
👀 Reading hidden code
368 μs

DisplayOnly

md"## DisplayOnly"
👀 Reading hidden code
177 μs
skip_as_script (generic function with 1 method)
function skip_as_script(m::Module)
if isdefined(m, :PlutoForceDisplay)
return m.PlutoForceDisplay
else
isdefined(m, :PlutoRunner) && parentmodule(m) == Main
end
end
👀 Reading hidden code
533 μs
@skip_as_script
@displayonly expression

Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects.

"""
@displayonly expression

Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects.
"""
macro skip_as_script(ex) skip_as_script(__module__) ? esc(ex) : nothing end
👀 Reading hidden code
721 μs
@only_as_script

The opposite of @skip_as_script

"The opposite of `@skip_as_script`"
macro only_as_script(ex) skip_as_script(__module__) ? nothing : esc(ex) end
👀 Reading hidden code
789 μs
2
@skip_as_script x = 2
👀 Reading hidden code
16.4 μs
1 + 1 == x
@skip_as_script @test 1 + 1 == x
👀 Reading hidden code
175 μs
1 + 1 + 1 == x
@skip_as_script @test 1 + 1 + 1 == x
👀 Reading hidden code
164 μs
throw("Oh my god") == x
Error: "Oh my god"
@skip_as_script @test throw("Oh my god") == x
👀 Reading hidden code
4.8 ms

Track

md"## Track"
👀 Reading hidden code
169 μs
sleep(0.1)
101.0 ms
20 frames in @code_typed
@skip_as_script @track sleep(0.1)
👀 Reading hidden code
380 ms
Tracked
begin
Base.@kwdef struct Tracked
expr
value
time
bytes
times_ran = 1
which = nothing
code_info = nothing
end
function Base.show(io::IO, mime::MIME"text/html", value::Tracked)
times_ran = if value.times_ran === 1
""
else
"""<span style="opacity: 0.5"> ($(value.times_ran)×)</span>"""
end
# method = sprint(show, MIME("text/plain"), value.which)
code_info = if value.code_info ≠ nothing
codelength = length(value.code_info.first.code)
"$(codelength) frames in @code_typed"
else
""
end
color = if value.time > 1
"red"
elseif value.time > 0.001
"orange"
elseif value.time > 0.0001
"blue"
else
"green"
end
show(io, mime, HTML("""
<div
style="
display: flex;
flex-direction: row;
align-items: center;
"
>
<div
style="
width: 12px;
height: 12px;
border-radius: 50%;
background-color: $(color);
"
></div>
<div style="width: 12px"></div>
<div>
<code
class="language-julia"
style="
background-color: transparent;
filter: grayscale(1) brightness(0.8);
"
>$(value.expr)</code>
<div style="
font-family: monospace;
font-size: 12px;
color: $(color);
">
$(prettytime(value.time * 1e9 / value.times_ran))
$(times_ran)
</div>
<div style="
font-family: monospace;
font-size: 12px;
color: gray;
">$(code_info)</div>

</div>
</div>
"""))
end
Tracked
end
👀 Reading hidden code
36.9 ms
@track (macro with 1 method)
macro track(expr)
times_ran_expr = :(1)
expr_to_show = expr
if expr.head == :for
@assert expr.args[1].head == :(=)
times_ran_expr = expr.args[1].args[2]
expr_to_show = expr.args[2].args[2]
end

Tracked # reference so that baby Pluto understands
quote
local times_ran = length($(esc(times_ran_expr)))
local value, time, bytes = @timed $(esc(expr))
local method = nothing
local code_info = nothing
try
# Uhhh
method = @which $(expr_to_show)
code_info = @code_typed $(expr_to_show)
catch nothing end
Tracked(
expr=$(QuoteNode(expr_to_show)),
value=value,
time=time,
bytes=bytes,
times_ran=times_ran,
which=method,
code_info=code_info
)
end
end
👀 Reading hidden code
1.8 ms
prettytime (generic function with 1 method)
function prettytime(time_ns::Number)
suffices = ["ns", "μs", "ms", "s"]
current_amount = time_ns
suffix = ""
for current_suffix in suffices
if current_amount >= 1000.0
current_amount = current_amount / 1000.0
else
suffix = current_suffix
break
end
end
# const roundedtime = time_ns.toFixed(time_ns >= 100.0 ? 0 : 1)
roundedtime = if current_amount >= 100.0
round(current_amount; digits=0)
else
round(current_amount; digits=1)
end
return "$(roundedtime) $(suffix)"
end
👀 Reading hidden code
1.5 ms

Table of contents

md"""
## Table of contents
"""
👀 Reading hidden code
172 μs
"@media screen and (min-width: 1081px) {\n\t.plutoui-toc.aside {\n\t\tposition:fixed; \n\t\tright: 1rem;\n\t\ttop: 5rem; \n\t\twidth:25%; \n\t\tpadding: 10px;\n\t\tborder: 3px solid rgba(0, 0, 0, 0.15);\n\t\tborder-radius: 10px;\n\t\tbox-shadow: 0 0 11px 0px #00000010;\n\t\t/* That is, viewport minus top minus Live Docs */\n\t\tma" ⋯ 790 bytes ⋯ " 0px;\n}\n.plutoui-toc.indent section a.H2 {\n\tpadding-left: 10px;\n}\n.plutoui-toc.indent section a.H3 {\n\tpadding-left: 20px;\n}\n.plutoui-toc.indent section a.H4 {\n\tpadding-left: 30px;\n}\n.plutoui-toc.indent section a.H5 {\n\tpadding-left: 40px;\n}\n.plutoui-toc.indent section a.H6 {\n\tpadding-left: 50px;\n}\n"
👀 Reading hidden code
113 μs
#1 (generic function with 1 method)
👀 Reading hidden code
625 μs
HTML("""
<script>
$(toc_js((;title="Table of Contents", indent=true, depth=3, aside=true)))
</script>
<style>
$(toc_css)
</style>
""")
👀 Reading hidden code
11.7 ms