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
239 μs
👀 Reading hidden code
begin
if !@isdefined(PlutoRunner)
import ..PlutoRunner
end
import Markdown
end
4.1 ms

Two state objects

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


# TODO: use GlobalRef instead
FunctionName = Array{Symbol,1}
14.7 μs
👀 Reading hidden code
struct FunctionNameSignaturePair
canonicalized_head::Any
end
1.5 ms
👀 Reading hidden code
Base.:(==)(a::FunctionNameSignaturePair, b::FunctionNameSignaturePair) = a.name == b.name && a.canonicalized_head == b.canonicalized_head
581 μs
👀 Reading hidden code
Base.hash(a::FunctionNameSignaturePair, h::UInt) = hash(a.name, hash(a.canonicalized_head, h))
449 μ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
10.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.2 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
505 μ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.5 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
632 μ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
597 μs
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
732 μs
Base.push!(x::Set) = x
👀 Reading hidden code
326 μs

Helper functions

👀 Reading hidden code
173 μs
# from the source code: https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm#L9
const modifiers = [:(+=), :(-=), :(*=), :(/=), :(//=), :(^=), :(÷=), :(%=), :(<<=), :(>>=), :(>>>=), :(&=), :(⊻=), :(≔), :(⩴), :(≕)]
👀 Reading hidden code
207 μ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
740 μ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
823 μ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.1 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
974 μ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
400 μs
all_underscores (generic function with 1 method)
all_underscores(s::Symbol) = all(isequal('_'), string(s))
👀 Reading hidden code
429 μ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
458 μs
uncurly! (generic function with 4 methods)
uncurly!(s::Symbol, scopestate=nothing)::Symbol = s
👀 Reading hidden code
664 μ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
952 μs
split_funcname (generic function with 2 methods)
function split_funcname(funcname_ex::QuoteNode)::FunctionName
split_funcname(funcname_ex.value)
end
👀 Reading hidden code
432 μs
split_funcname (generic function with 3 methods)
function split_funcname(funcname_ex::GlobalRef)::FunctionName
split_funcname(funcname_ex.name)
end
👀 Reading hidden code
479 μ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
448 μ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
461 μs
is_just_dots (generic function with 2 methods)
is_just_dots(::Union{QuoteNode,Symbol,GlobalRef}) = true
👀 Reading hidden code
384 μs
is_just_dots (generic function with 3 methods)
is_just_dots(::Any) = false
👀 Reading hidden code
323 μ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
422 μ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
996 μ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
974 μs
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
701 μ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
384 μs
assign_to_kw (generic function with 1 method)
assign_to_kw(e::Expr) = e.head == :(=) ? Expr(:kw, e.args...) : e
👀 Reading hidden code
531 μs
assign_to_kw (generic function with 2 methods)
assign_to_kw(x::Any) = x
👀 Reading hidden code
377 μ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.3 ms

Main recursive function

Spaghetti code for a spaghetti problem 🍝

👀 Reading hidden code
235 μ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
407 μ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
810 μ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
50.7 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
485 μ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
668 μs
explore_funcdef! (generic function with 3 methods)
function explore_funcdef!(::Any, ::ScopeState)::Tuple{FunctionName,SymbolsState}
Symbol[], SymbolsState()
end
👀 Reading hidden code
470 μ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
57.6 ms
const can_macroexpand = can_macroexpand_no_bind ∪ Set(Symbol.(["@bind", "PlutoRunner.@bind"]))
👀 Reading hidden code
261 μ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
675 μ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
423 μ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
614 μ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.9 ms
maybe_macroexpand (generic function with 2 methods)
maybe_macroexpand(ex::Any; kwargs...) = ex
👀 Reading hidden code
975 μs

Canonicalize function definitions

👀 Reading hidden code
173 μ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.8 ms
canonalize (generic function with 1 method)
# for `function g end`
canonalize(::Symbol) = nothing
👀 Reading hidden code
357 μ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.2 ms
hide_argument_name (generic function with 2 methods)
hide_argument_name(::Symbol) = Expr(:(::), nothing, :Any)
👀 Reading hidden code
421 μs
hide_argument_name (generic function with 3 methods)
hide_argument_name(x::Any) = x
👀 Reading hidden code
402 μs

Utility functions

👀 Reading hidden code
173 μ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.8 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.7 ms
UsingsImports
Base.@kwdef struct UsingsImports
usings::Set{Expr}=Set{Expr}()
imports::Set{Expr}=Set{Expr}()
end
👀 Reading hidden code
2.9 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.6 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.7 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
628 μ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
640 μ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
977 μs
is_toplevel_expr (generic function with 2 methods)
is_toplevel_expr(::Any)::Bool = false
👀 Reading hidden code
372 μ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
677 μ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
357 μs

Tests

👀 Reading hidden code
188 μs

Basics

👀 Reading hidden code
167 μs
testee(:a, [:a], [], [], [])
👀 Reading hidden code
101 ms
testee(:(1 + 1), [], [], [:+], [])
👀 Reading hidden code
706 ms
testee(:(sqrt(1)), [], [], [:sqrt], [])
👀 Reading hidden code
304 μs
testee(:(x = 3), [], [:x], [], [])
👀 Reading hidden code
106 ms
testee(:(x = x), [:x], [:x], [], [])
👀 Reading hidden code
6.1 ms
testee(:(x = 1 + y), [:y], [:x], [:+], [])
👀 Reading hidden code
3.4 ms
testee(:(x = +((a...))), [:a], [:x], [:+], [])
👀 Reading hidden code
3.2 ms
testee(:(1:3), [], [], [:(:)], [])
👀 Reading hidden code
347 μs

Bad code

👀 Reading hidden code
177 μs
👀 Reading hidden code
12.8 μs
testee(:(123 = x), [:x], [], [], [])
👀 Reading hidden code
3.1 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
@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
@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
@test_nowarn testee(:(function f() global g(x) = x; end), [], [], [], [], verbose=false)
👀 Reading hidden code
---

Lists and structs

👀 Reading hidden code
220 μs
testee(:(1:3), [], [], [:(:)], [])
👀 Reading hidden code
310 μs
testee(:(a[1:3, 4]), [:a], [], [:(:)], [])
👀 Reading hidden code
311 μs
testee(:(a[b]), [:a, :b], [], [], [])
👀 Reading hidden code
297 μs
testee(:([a[1:3, 4]; b[5]]), [:b, :a], [], [:(:)], [])
👀 Reading hidden code
343 μs
testee(:(a.someproperty), [:a], [], [], [])
👀 Reading hidden code
4.5 ms
testee(:([a..., b]), [:a, :b], [], [], [])
👀 Reading hidden code
262 μs
testee(:(struct a b c end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
389 ms
testee(:(let struct a b c end end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
36.1 ms
testee(:(module a f(x) = begin x end z = r end), [], [:a], [], [])
👀 Reading hidden code
270 μs

Types

👀 Reading hidden code
179 μs
testee(:(x::Foo = 3), [:Foo], [:x], [], [])
👀 Reading hidden code
95.2 ms
testee(:(x::Foo), [:x, :Foo], [], [], [])
👀 Reading hidden code
264 μs
testee(:((a::Foo, b::String = 1, "2")), [:Foo, :String], [:a, :b], [], [])
👀 Reading hidden code
77.5 ms
testee(:(Foo[]), [:Foo], [], [], [])
👀 Reading hidden code
241 μs
testee(:(x isa Foo), [:x, :Foo], [], [:isa], [])
👀 Reading hidden code
292 μs
testee(:(A{B} = B), [], [:A], [], [])
👀 Reading hidden code
20.5 ms
testee(:(A{T} = Union{T, Int}), [:Int, :Union], [:A], [], [])
👀 Reading hidden code
344 μs
testee(:(abstract type a end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
5.0 ms
testee(:(abstract type a <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
392 μs
testee(:(abstract type a <: b{C} end), [], [:a], [], [:a => ([:b, :C], [], [], [])])
👀 Reading hidden code
387 μs
testee(:(abstract type a{T} end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
350 μs
testee(:(abstract type a{T, S} end), [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
4.4 ms
testee(:(abstract type a{T} <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
364 μs
testee(:(abstract type a{T} <: b{T} end), [], [:a], [], [:a => ([:b], [], [], [])])
👀 Reading hidden code
389 μ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
125 μs
testee(e, [], [:a], [], [:a => ([], [], [], [])])
👀 Reading hidden code
48.1 ms
testee(:(struct a <: b c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
12.9 ms
testee(:(struct a{T, S} c::T d::Foo end), [], [:a], [], [:a => ([:Foo], [], [], [])])
👀 Reading hidden code
475 μs
testee(:(struct a{T} <: b c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
476 μs
testee(:(struct a{T} <: b{T} c d::Foo end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
👀 Reading hidden code
483 μs
testee(:(struct a c a(x = y) = begin new(x, z) end end), [], [:a], [], [:a => ([:y, :z], [], [:new], [])])
👀 Reading hidden code
4.2 ms
👀 Reading hidden code
11.2 μs

Assignment operator & modifiers

👀 Reading hidden code
200 μs
testee(:(a = a), [:a], [:a], [], [])
👀 Reading hidden code
246 μs
testee(:(a = a + 1), [:a], [:a], [:+], [])
👀 Reading hidden code
338 μs
testee(:(x = (a = a + 1)), [:a], [:a, :x], [:+], [])
👀 Reading hidden code
315 μs
testee(:(const a = b), [:b], [:a], [], [])
👀 Reading hidden code
320 μs
testee(:(f(x) = begin x end), [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
72.1 ms
testee(:(a[b, c, :] = d), [:a, :b, :c, :d, :(:)], [], [], [])
👀 Reading hidden code
4.6 ms
testee(:(a.b = c), [:a, :c], [], [], [])
👀 Reading hidden code
290 μs
testee(:(f(a, b = c, d = e; f = g)), [:a, :c, :e, :g], [], [:f], [])
👀 Reading hidden code
318 μs
testee(:(a += 1), [:a], [:a], [:+], [])
👀 Reading hidden code
301 μs
testee(:(a >>>= 1), [:a], [:a], [:>>>], [])
👀 Reading hidden code
355 μs
testee(:(a ⊻= 1), [:a], [:a], [:⊻], [])
👀 Reading hidden code
333 μs
testee(:(a[1] += 1), [:a], [], [:+], [])
👀 Reading hidden code
325 μs
testee(:(x = let a = 1 a += b end), [:b], [:x], [:+], [])
👀 Reading hidden code
380 μs
testee(:(_ = a + 1), [:a], [], [:+], [])
👀 Reading hidden code
307 μs
testee(:(a = _ + 1), [], [:a], [:+], [])
👀 Reading hidden code
326 μs

Tuples

👀 Reading hidden code
191 μs
testee(:((a, b)), [:a, :b], [], [], [])
👀 Reading hidden code
258 μs
testee(:((a = b, c = 2, d = 123)), [:b], [], [], [])
👀 Reading hidden code
38.8 ms
testee(:((a = b,)), [:b], [], [], [])
👀 Reading hidden code
259 μs
testee(:((a, b = 1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
368 μs
testee(:((a, _, c, __ = 1, 2, 3, _d)), [:_d], [:a, :c], [], [])
👀 Reading hidden code
13.0 ms
testee(:(const (a, b) = (1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
373 μs
testee(:(((a, b) = 1, 2)), [], [:a, :b], [], [])
👀 Reading hidden code
6.1 ms
testee(:((a = b, c)), [:b, :c], [:a], [], [])
👀 Reading hidden code
355 μs
testee(:((a, b = c)), [:c], [:a, :b], [], [])
👀 Reading hidden code
371 μs
testee(:(a = (b, c)), [:b, :c], [:a], [], [])
👀 Reading hidden code
281 μs
testee(:((a, (b, c) = [e, [f, g]])), [:e, :f, :g], [:a, :b, :c], [], [])
👀 Reading hidden code
419 μs
testee(:(((x, y), a, (b, c) = z, e, (f, g))), [:z, :e, :f, :g], [:x, :y, :a, :b, :c], [], [])
👀 Reading hidden code
486 μ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.2 ms
testee(:((a[i], b.r) = (c.d, 2)), [:a, :b, :i, :c], [], [], [])
👀 Reading hidden code
396 μs

Broadcasting

👀 Reading hidden code
172 μs
testee(:(a .= b), [:b, :a], [], [], [])
👀 Reading hidden code
268 μs
testee(:(a .+= b), [:b, :a], [], [:+], [])
👀 Reading hidden code
318 μs
testee(:(a[i] .+= b), [:b, :a, :i], [], [:+], [])
👀 Reading hidden code
321 μs
testee(:(a .+ b ./ sqrt.(c, d)), [:a, :b, :c, :d], [], [:+, :/, :sqrt], [])
👀 Reading hidden code
376 μs

for & while

👀 Reading hidden code
192 μs
testee(:(for k = 1:n k + s end), [:n, :s], [], [:+, :(:)], [])
👀 Reading hidden code
384 μs
testee(:(for k = 1:2, r = 3:4 global z = k + r end), [], [:z], [:+, :(:)], [])
👀 Reading hidden code
430 μs
testee(:(while k < 2 r = w global z = k + r end), [:k, :w], [:z], [:+, :<], [])
👀 Reading hidden code
438 μs

try & catch

👀 Reading hidden code
186 μs
testee(:(try a = b + 1 catch end), [:b], [], [:+], [])
👀 Reading hidden code
11.8 ms
testee(:(try a() catch e e end), [], [], [:a], [])
👀 Reading hidden code
8.4 ms
testee(:(try a() catch e end), [:e], [], [:a], [])
👀 Reading hidden code
348 μs
testee(:(try a + 1 catch a a end), [:a], [], [:+], [])
👀 Reading hidden code
359 μs
testee(:(try 1 catch e e finally a end), [:a], [], [], [])
👀 Reading hidden code
9.1 ms
testee(:(try 1 finally a end), [:a], [], [], [])
👀 Reading hidden code
312 μs

Comprehensions

👀 Reading hidden code
170 μs
testee(:([sqrt(s) for s = 1:n]), [:n], [], [:sqrt, :(:)], [])
👀 Reading hidden code
375 μs
testee(:([sqrt(s + r) for s = 1:n, r = k]), [:n, :k], [], [:sqrt, :(:), :+], [])
👀 Reading hidden code
400 μs
testee(:([s + j + r + m for s = 1:3 for j = 4:5 for (r, l) = [(1, 2)]]), [:m], [], [:+, :(:)], [])
👀 Reading hidden code
586 μs
testee(:([a for a = b if a != 2]), [:b], [], [:!=], [])
👀 Reading hidden code
41.3 ms
testee(:([a for a = f() if g(a)]), [], [], [:f, :g], [])
👀 Reading hidden code
407 μs
testee(:([c(a) for a = f() if g(a)]), [], [], [:c, :f, :g], [])
👀 Reading hidden code
373 μs
testee(:([a for a = a]), [:a], [], [], [])
👀 Reading hidden code
326 μs
testee(:(for a = a a end), [:a], [], [], [])
👀 Reading hidden code
309 μs
testee(:(let a = a a end), [:a], [], [], [])
👀 Reading hidden code
292 μs
testee(:(let a = a end), [:a], [], [], [])
👀 Reading hidden code
322 μs
testee(:(let a = b end), [:b], [], [], [])
👀 Reading hidden code
305 μs
testee(:(a = a), [:a], [:a], [], [])
👀 Reading hidden code
245 μs
testee(:(a = [a for a = a]), [:a], [:a], [], [])
👀 Reading hidden code
318 μs

Multiple expressions

👀 Reading hidden code
175 μs
testee(:(x = let r = 1 r + r end), [], [:x], [:+], [])
👀 Reading hidden code
378 μs
testee(quote let r = 1 r + r end r = 2 end, [], [:r], [:+], [])
👀 Reading hidden code
393 μs
testee(quote k = 2 123 end, [], [:k], [], [])
👀 Reading hidden code
278 μs
testee(quote a = 1 b = a + 1 end, [], [:a, :b], [:+], [])
👀 Reading hidden code
332 μs
testee(Meta.parse("a = 1; b = a + 1"), [], [:a, :b], [:+], [])
👀 Reading hidden code
3.7 ms
testee(:(a = (b = 1)), [], [:a, :b], [], [])
👀 Reading hidden code
262 μs
testee(:(let k = 2 123 end), [], [], [], [])
👀 Reading hidden code
304 μs
testee(:(let k() = begin 2 end end), [], [], [], [])
👀 Reading hidden code
371 μs

Functions

👀 Reading hidden code
169 μs
testee(:(function g() r = 2 r end), [], [], [], [:g => ([], [], [], [])])
👀 Reading hidden code
388 μs
testee(:(function g end), [], [], [], [:g => ([], [], [], [])])
👀 Reading hidden code
332 μs
testee(:(function f() g(x) = begin x end end), [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
429 μs
testee(:(function f(x, y = 1; r, s = 3 + 3) r + s + x * y * z end), [], [], [], [:f => ([:z], [], [:+, :*], [])])
👀 Reading hidden code
61.2 ms
testee(:(function f(x) x * y * z end), [], [], [], [:f => ([:y, :z], [], [:*], [])])
👀 Reading hidden code
426 μs
testee(:(function f(x) x = x / 3 x end), [], [], [], [:f => ([], [], [:/], [])])
👀 Reading hidden code
461 μs
testee(quote function f(x) a end function f(x, y) b end end, [], [], [], [:f => ([:a, :b], [], [], [])])
👀 Reading hidden code
509 μs
testee(:(function f(x, args...; kwargs...) return [x, y, args..., kwargs...] end), [], [], [], [:f => ([:y], [], [], [])])
👀 Reading hidden code
485 μs
testee(:(function f(x; y = x) y + x end), [], [], [], [:f => ([], [], [:+], [])])
👀 Reading hidden code
28.6 ms
testee(:(function (A::MyType)(x; y = x) y + x end), [], [], [], [:MyType => ([], [], [:+], [])])
👀 Reading hidden code
497 μs
testee(:(f(x, y = a + 1) = begin x * y * z end), [], [], [], [:f => ([:z, :a], [], [:*, :+], [])])
👀 Reading hidden code
493 μs
testee(quote f() = begin 1 end f end, [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
367 μs
testee(quote f() = begin 1 end f() end, [], [], [], [:f => ([], [], [], [])])
👀 Reading hidden code
397 μ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
629 μs
testee(:(Base.show() = begin 0 end), [:Base], [], [], [[:Base, :show] => ([], [], [], [])])
👀 Reading hidden code
55.2 ms
testee(:((begin x p end->begin f(x + p) end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
👀 Reading hidden code
23.4 ms
testee(:((begin x p end->begin f(x + p) end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
👀 Reading hidden code
437 μs
testee(:(minimum(x) do (a, b) a + b end), [:x], [], [:minimum], [:anon => ([], [], [:+], [])])
👀 Reading hidden code
505 μs
testee(:(f = (x->begin x * y end)), [], [:f], [], [:anon => ([:y], [], [:*], [])])
👀 Reading hidden code
485 μs
testee(:(f = ((x, y)->begin x * y end)), [], [:f], [], [:anon => ([], [], [:*], [])])
👀 Reading hidden code
468 μs
testee(:(f = ((x, y = a + 1)->begin x * y end)), [], [:f], [], [:anon => ([:a], [], [:*, :+], [])])
👀 Reading hidden code
477 μs
testee(:(((((a, b), c), (d, e))->begin a * b * c * d * e * f end)), [], [], [], [:anon => ([:f], [], [:*], [])])
👀 Reading hidden code
501 μs
testee(:((a...->begin f(a...) end)), [], [], [], [:anon => ([], [], [:f], [])])
👀 Reading hidden code
476 μs
testee(:(f = (args...->begin [args..., y] end)), [], [:f], [], [:anon => ([:y], [], [], [])])
👀 Reading hidden code
423 μs
testee(:(f = ((x, args...; kwargs...)->begin [x, y, args..., kwargs...] end)), [], [:f], [], [:anon => ([:y], [], [], [])])
👀 Reading hidden code
523 μs
testee(:(f = function (a, b) a + b * n end), [:n], [:f], [:+, :*], [])
👀 Reading hidden code
449 μs
testee(:(f = function () a + b end), [:a, :b], [:f], [:+], [])
👀 Reading hidden code
391 μs
testee(:(func(a)), [:a], [], [:func], [])
👀 Reading hidden code
278 μs
testee(:(func(a; b = c)), [:a, :c], [], [:func], [])
👀 Reading hidden code
372 μs
testee(:(func(a, b = c)), [:a, :c], [], [:func], [])
👀 Reading hidden code
296 μs
testee(:(√b), [:b], [], [:√], [])
👀 Reading hidden code
282 μs
testee(:((funcs[i])(b)), [:funcs, :i, :b], [], [], [])
👀 Reading hidden code
267 μs
testee(:((f(a))(b)), [:a, :b], [], [:f], [])
👀 Reading hidden code
327 μs
testee(:((f(a)).b()), [:a], [], [:f], [])
👀 Reading hidden code
30.7 ms
testee(:(a.b(c)), [:a, :c], [], [[:a, :b]], [])
👀 Reading hidden code
16.1 ms
testee(:(a.b.c(d)), [:a, :d], [], [[:a, :b, :c]], [])
👀 Reading hidden code
370 μs
testee(:((a.b(c))(d)), [:a, :c, :d], [], [[:a, :b]], [])
👀 Reading hidden code
362 μs
testee(:((a.b(c)).d(e)), [:a, :c, :e], [], [[:a, :b]], [])
👀 Reading hidden code
395 μs
testee(:((a.b[c]).d(e)), [:a, :c, :e], [], [], [])
👀 Reading hidden code
304 μs

Functions & types

👀 Reading hidden code
170 μs
testee(:(function f(y::Int64 = a)::String string(y) end), [], [], [], [:f => ([:String, :Int64, :a], [], [:string], [])])
👀 Reading hidden code
435 μs
testee(quote f(a::A)::C = begin a.a end end, [], [], [], [:f => ([:A, :C], [], [], [])])
👀 Reading hidden code
437 μs
testee(:(function f(x::T; k = 1) where T return x + 1 end), [], [], [], [:f => ([], [], [:+], [])])
👀 Reading hidden code
61.1 ms
testee(:(function f(x::T; k = 1) where {T, S <: R} return x + 1 end), [], [], [], [:f => ([:R], [], [:+], [])])
👀 Reading hidden code
515 μs
testee(:(f(x)::String = begin x end), [], [], [], [:f => ([:String], [], [], [])])
👀 Reading hidden code
403 μs
testee(:(@MIME_str()), [], [], [Symbol("@MIME_str")], [])
👀 Reading hidden code
186 ms
testee(:(function f(::@MIME_str()) 1 end), [], [], [], [:f => ([], [], [Symbol("@MIME_str")], [])])
👀 Reading hidden code
14.5 ms
testee(:((a(a::AbstractArray{T}) where T) = begin 5 end), [], [], [], [:a => ([:AbstractArray], [], [], [])])
👀 Reading hidden code
443 μs
testee(:((a(a::AbstractArray{T, R}) where {T, S}) = begin a + b end), [], [], [], [:a => ([:AbstractArray, :b, :R], [], [:+], [])])
👀 Reading hidden code
465 μs
testee(:(f(::A) = begin 1 end), [], [], [], [:f => ([:A], [], [], [])])
👀 Reading hidden code
349 μs
testee(:(f(::A, ::B) = begin 1 end), [], [], [], [:f => ([:A, :B], [], [], [])])
👀 Reading hidden code
388 μs
testee(:(f(a::A, ::B, c::C...) = begin a + c end), [], [], [], [:f => ([:A, :B, :C], [], [:+], [])])
👀 Reading hidden code
473 μs
testee(:((obj::MyType)(x, y) = begin x + z end), [], [], [], [:MyType => ([:z], [], [:+], [])])
👀 Reading hidden code
480 μs
testee(:((obj::MyType)() = begin 1 end), [], [], [], [:MyType => ([], [], [], [])])
👀 Reading hidden code
351 μs
testee(:((obj::MyType)(x, args...; kwargs...) = begin [x, y, args..., kwargs...] end), [], [], [], [:MyType => ([:y], [], [], [])])
👀 Reading hidden code
420 μs
testee(:(function (obj::MyType)(x, y) x + z end), [], [], [], [:MyType => ([:z], [], [:+], [])])
👀 Reading hidden code
429 μs
testee(quote struct MyType x::String end (obj::MyType)(y) = begin obj.x + y end end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
👀 Reading hidden code
571 μs
testee(quote struct MyType x::String end function (obj::MyType)(y) obj.x + y end end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
👀 Reading hidden code
552 μs
testee(:((::MyType)(x, y) = begin x + y end), [], [], [], [:MyType => ([], [], [:+], [])])
👀 Reading hidden code
443 μs

Scope modifiers

👀 Reading hidden code
171 μs
testee(:(let global (a, b) = (1, 2) end), [], [:a, :b], [], [])
👀 Reading hidden code
410 μ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
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
@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
319 μs
testee(:(let global k += 3 end), [:k], [:k], [:+], [])
👀 Reading hidden code
350 μs
testee(:(let global k k = 4 end), [], [:k], [], [])
👀 Reading hidden code
314 μs
testee(:(let global k b = 5 end), [], [], [], [])
👀 Reading hidden code
319 μs
testee(:(let a = 1, b = 2 show(a + b) end), [], [], [:show, :+], [])
👀 Reading hidden code
378 μs
testee(quote local (a, b) = (1, 2) end, [], [], [], [])
👀 Reading hidden code
394 μs
testee(quote local a = (b = 1) end, [], [:b], [], [])
👀 Reading hidden code
309 μs
testee(quote local k = 3 end, [], [], [], [])
👀 Reading hidden code
278 μs
testee(quote local k = r end, [:r], [], [], [])
👀 Reading hidden code
278 μs
testee(quote local k = 3 k b = 4 end, [], [:b], [], [])
👀 Reading hidden code
324 μs
testee(quote local k += 3 end, [], [], [:+], [])
👀 Reading hidden code
381 μs
testee(quote local k k = 4 end, [], [], [], [])
👀 Reading hidden code
290 μs
testee(quote local k b = 5 end, [], [:b], [], [])
👀 Reading hidden code
302 μs
testee(quote local r[1] = 5 end, [:r], [], [], [])
👀 Reading hidden code
318 μ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(:(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
438 μs
testee(:((begin x = 1 end, y)), [:y], [:x], [], [])
👀 Reading hidden code
330 μs
testee(:(x = let global a += 1 end), [:a], [:x, :a], [:+], [])
👀 Reading hidden code
399 μs

import & using

👀 Reading hidden code
180 μs
testee(:(using Plots), [], [:Plots], [], [])
👀 Reading hidden code
43.2 ms
testee(:(using Plots.ExpressionExplorer), [], [:ExpressionExplorer], [], [])
👀 Reading hidden code
262 μs
testee(:(using JSON, UUIDs), [], [:JSON, :UUIDs], [], [])
👀 Reading hidden code
256 μs
testee(:(import Pluto), [], [:Pluto], [], [])
👀 Reading hidden code
243 μs
testee(:(import Pluto: wow, wowie), [], [:wow, :wowie], [], [])
👀 Reading hidden code
7.5 ms
testee(:(import Pluto.ExpressionExplorer.wow, Plutowie), [], [:wow, :Plutowie], [], [])
👀 Reading hidden code
301 μs
testee(:(import .Pluto: wow), [], [:wow], [], [])
👀 Reading hidden code
300 μs
testee(:(import ..Pluto: wow), [], [:wow], [], [])
👀 Reading hidden code
258 μs
testee(:(let import Pluto.wow, Dates end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
320 μs
testee(:(while false import Pluto.wow, Dates end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
9.5 ms
testee(:(try using Pluto.wow, Dates catch end), [], [:wow, :Dates], [], [])
👀 Reading hidden code
345 μs
testee(:(module A import B end), [], [:A], [], [])
👀 Reading hidden code
258 μs

Foreign macros

parameterizedfunctions

👀 Reading hidden code
212 μ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.4 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
183 μs
testee(:(@functor()), [], [:Asdf], [Symbol("@functor")], [])
👀 Reading hidden code
1.2 ms

Symbolics.jl

👀 Reading hidden code
181 μs
testee(:(@variables(b, c)), [], [:a, :b, :c], [Symbol("@variables")], [])
👀 Reading hidden code
141 ms
testee(:(@variables()), [], [:a, :b, :c], [Symbol("@variables")], [])
👀 Reading hidden code
1.5 ms
testee(:(@variables(b[1:2], c(t), d(..))), [], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
👀 Reading hidden code
14.1 ms
testee(:(@variables(b[1:x], (c[1:10])(t), d(..))), [:x], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
👀 Reading hidden code
4.6 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
@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
188 μs
👀 Reading hidden code
11.3 μs
👀 Reading hidden code
11.5 μs
👀 Reading hidden code
11.8 μs
👀 Reading hidden code
21.8 μs
👀 Reading hidden code
13.5 μs
👀 Reading hidden code
19.9 μs
👀 Reading hidden code
19.8 μs

Macros

👀 Reading hidden code
227 μs
testee(:(@time()), [], [:a], [Symbol("@time")], [])
👀 Reading hidden code
310 μs
testee(:(@f(x)), [:x, :z], [], [Symbol("@f")], [])
👀 Reading hidden code
32.2 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
461 μs
testee(:(@gensym(b, c)), [], [:a, :b, :c], [:gensym, Symbol("@gensym")], [])
👀 Reading hidden code
31.6 ms
testee(:(Base.@gensym(b, c)), [:Base], [:a, :b, :c], [:gensym, [:Base, Symbol("@gensym")]], [])
👀 Reading hidden code
45.1 ms
testee(:(Base.@kwdef()), [:Base], [:A], [[:Base, Symbol("@kwdef")], [:Base, Symbol("@__doc__")]], [:A => ([:Int, :two], [], [], [])])
👀 Reading hidden code
1.1 ms
testee(quote Core.@doc f(x) = begin x end end, [], [], [Symbol("@doc")], [:f => ([], [], [], [])])
👀 Reading hidden code
87.2 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.5 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}())

47.2 ms
testee(:(@asdf(b = x2, c = x3)), [:x1, :x2, :x3], [:a], [Symbol("@asdf")], [])
👀 Reading hidden code
1.3 ms
testee(:(@einsum()), [:x, :y, :Float64], [:a], [[Symbol("@einsum")], [:*]], [])
👀 Reading hidden code
231 ms
testee(:(@tullio(init = z)), [:x, :k, :z], [:a], [[Symbol("@tullio")], [:f], [:*], [:+]], [])
👀 Reading hidden code
64.4 ms
testee(:(Pack.@asdf()), [:x, :y, :k, :Pack, :Float64], [:a], [[:Pack, Symbol("@asdf")], [:/], [:log]], [])
👀 Reading hidden code
600 μ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
34.9 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}())

147 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}())

3.2 ms
testee(:(@html_str()), [], [], [Symbol("@html_str")], [])
👀 Reading hidden code
345 μs
testee(:(@md_str()), [:c], [:b], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
594 μs
testee(:(@md_str()), [:r], [], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
14.4 ms
testee(:(@md_str()), [], [], [:getindex, Symbol("@md_str")], [])
👀 Reading hidden code
451 μs
testee(:(macro a() end), [], [], [], [Symbol("@a") => ([], [], [], [])])
👀 Reading hidden code
8.8 ms
testee(:(macro a(b::Int) b end), [], [], [], [Symbol("@a") => ([:Int], [], [], [])])
👀 Reading hidden code
415 μs
testee(:(macro a(b::Int = c) end), [], [], [], [Symbol("@a") => ([:Int, :c], [], [], [])])
👀 Reading hidden code
440 μs
testee(:(macro a() b = c return b end), [], [], [], [Symbol("@a") => ([:c], [], [], [])])
👀 Reading hidden code
430 μs

String interpolation & expressions

👀 Reading hidden code
219 μs
testee(:("a $(b)"), [:b], [], [], [])
👀 Reading hidden code
275 μs
testee(:("a $(b = c)"), [:c], [:b], [], [])
👀 Reading hidden code
280 μs
testee(:(ex = :yayo), [], [:ex], [], [])
👀 Reading hidden code
4.6 ms
testee(:(ex = :(yayo + $r)), [], [:ex], [], [])
👀 Reading hidden code
285 μs
👀 Reading hidden code
14.6 μs

Extracting using and import

👀 Reading hidden code
236 μ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
271 μs
👀 Reading hidden code
16.3 ms
using_test_result.usings == Set{Expr}([:(using A), :(using .C: r)])
👀 Reading hidden code
64.1 ms
using_test_result.imports == Set{Expr}([:(import B), :(import ..D.E: f, g), :(import H.I, J, K.L)])
👀 Reading hidden code
265 μs
external_package_names(using_test_result) == Set{Symbol}([:A, :B, :H, :J, :K])
👀 Reading hidden code
15.2 ms
external_package_names(:(using Plots, Something.Else, .LocalModule)) == Set([:Plots, :Something])
👀 Reading hidden code
238 μs
external_package_names(:(import Plots.A: b, c)) == Set([:Plots])
👀 Reading hidden code
216 μs

Appendix

👀 Reading hidden code
228 μ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
29.6 ms
easy_symstate (generic function with 1 method)
👀 Reading hidden code
2.1 ms

Visual testing

👀 Reading hidden code
190 μs
abstract type TestResult end
👀 Reading hidden code
321 μs
Any
const Code = Any
👀 Reading hidden code
103 μs
struct Pass <: TestResult
expr::Code
end
👀 Reading hidden code
1.2 ms
abstract type Fail <: TestResult end
👀 Reading hidden code
347 μs
struct Wrong <: Fail
expr::Code
result
end
👀 Reading hidden code
1.4 ms
struct Error <: Fail
expr::Code
error
end
👀 Reading hidden code
1.9 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
656 μ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
560 μ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
596 μs
# 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
5.3 ms
@test2 (macro with 1 method)
macro test2(expr)
quote nothing end
end
👀 Reading hidden code
408 μs
notebook1 == deepcopy(notebook1)
Error: UndefVarError: notebook1 not defined
@skip_as_script @test notebook1 == deepcopy(notebook1)
👀 Reading hidden code
6.4 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.5 ms
remove_linenums (generic function with 2 methods)
remove_linenums(x) = x
👀 Reading hidden code
397 μs

DisplayOnly

md"## DisplayOnly"
👀 Reading hidden code
185 μ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
601 μ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
775 μ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
793 μs
2
@skip_as_script x = 2
👀 Reading hidden code
21.3 μs
1 + 1 == x
@skip_as_script @test 1 + 1 == x
👀 Reading hidden code
171 μs
1 + 1 + 1 == x
@skip_as_script @test 1 + 1 + 1 == x
👀 Reading hidden code
185 μs
throw("Oh my god") == x
Error: "Oh my god"
@skip_as_script @test throw("Oh my god") == x
👀 Reading hidden code
4.3 ms

Track

md"## Track"
👀 Reading hidden code
182 μs
sleep(0.1)
101.0 ms
20 frames in @code_typed
@skip_as_script @track sleep(0.1)
👀 Reading hidden code
385 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
41.2 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.9 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.7 ms

Table of contents

md"""
## Table of contents
"""
👀 Reading hidden code
181 μ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
644 μ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