ExpressionExplorer
This notebook is part of Pluto's source code.
Two state objects
Vector{Symbol} (alias for Array{Symbol, 1})
SymbolsState
ScopeState
Helper functions
:+=
:-=
:*=
:/=
://=
:^=
:÷=
:%=
:<<=
:>>=
:>>>=
:&=
:⊻=
:≔
:⩴
:≕
:.+=
:.-=
:.*=
:./=
:.//=
:.^=
:.÷=
:.%=
:.<<=
:.>>=
:.>>>=
:.&=
:.⊻=
:.≔
:.⩴
:.≕
will_assign_global (generic function with 1 method)
will_assign_global (generic function with 2 methods)
get_global_assignees (generic function with 1 method)
get_assignees (generic function with 1 method)
get_assignees (generic function with 3 methods)
get_assignees (generic function with 2 methods)
all_underscores (generic function with 1 method)
Turn :(A{T}) into :A.
uncurly! (generic function with 2 methods)
uncurly! (generic function with 4 methods)
Turn :(Base.Submodule.f)
into [:Base, :Submodule, :f]
and :f
into [:f]
.
split_funcname (generic function with 2 methods)
split_funcname (generic function with 3 methods)
split_funcname (generic function with 5 methods)
is_just_dots (generic function with 1 method)
is_just_dots (generic function with 2 methods)
is_just_dots (generic function with 3 methods)
split_funcname (generic function with 4 methods)
Turn Symbol(".+")
into :(+)
Turn Symbol("sqrt.")
into :sqrt
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
.
is_joined_funcname (generic function with 1 method)
assign_to_kw (generic function with 1 method)
assign_to_kw (generic function with 2 methods)
Turn A[i] * B[j,K[l+m]]
into A[0] * B[0,K[0+0]]
to hide loop indices
Main recursive function
Spaghetti code for a spaghetti problem 🍝
explore! (generic function with 1 method)
explore! (generic function with 2 methods)
explore_inner_scoped (generic function with 1 method)
explore_funcdef! (generic function with 1 method)
explore_funcdef! (generic function with 2 methods)
explore_funcdef! (generic function with 3 methods)
Symbol("@enum")
Symbol("Base.@enum")
Symbol("@md_str")
Symbol("Markdown.@md_str")
Symbol("@gensym")
Symbol("@kwdef")
Symbol("@cmd")
Symbol("Base.@kwdef")
Symbol("Base.@gensym")
Symbol("@md_str")
Symbol("PlutoRunner.@bind")
Symbol("@gensym")
Symbol("@kwdef")
Symbol("@cmd")
Symbol("@enum")
Symbol("Base.@enum")
Symbol("Markdown.@md_str")
Symbol("@bind")
Symbol("Base.@kwdef")
Symbol("Base.@gensym")
macro_kwargs_as_kw (generic function with 1 method)
symbolics_mockexpand (generic function with 1 method)
is_symbolics_arg (generic function with 1 method)
maybe_untuple (generic function with 1 method)
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
maybe_macroexpand (generic function with 2 methods)
Canonicalize function definitions
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) ```
canonalize (generic function with 1 method)
hide_argument_name (generic function with 1 method)
hide_argument_name (generic function with 2 methods)
hide_argument_name (generic function with 3 methods)
Utility functions
Get the global references, assignment, function calls and function defintions inside an arbitrary expression.
try_compute_symbolreferences (generic function with 1 method)
UsingsImports
Preallocated version of compute_usings_imports
.
Given :(using Plots, Something.Else, .LocalModule)
, return Set([:Plots, :Something])
.
external_package_names (generic function with 2 methods)
Get the sets of using Module
and import Module
subexpressions that are contained in this expression.
Return whether the expression is of the form Expr(:toplevel, LineNumberNode(..), any)
.
is_toplevel_expr (generic function with 2 methods)
If the expression is a (simple) assignemnt at its root, return the assignee as Symbol
, return nothing
otherwise.
get_rootassignee (generic function with 4 methods)
Is this code simple enough that we can wrap it inside a function to boost performance? Look for PlutoRunner.Computer
to learn more.
can_be_function_wrapped (generic function with 2 methods)
Tests
Basics
testee(:a, [:a], [], [], [])
testee(:(1 + 1), [], [], [:+], [])
testee(:(sqrt(1)), [], [], [:sqrt], [])
testee(:(x = 3), [], [:x], [], [])
testee(:(x = x), [:x], [:x], [], [])
testee(:(x = 1 + y), [:y], [:x], [:+], [])
testee(:(x = +((a...))), [:a], [:x], [:+], [])
testee(:(1:3), [], [], [:(:)], [])
Bad code
testee(:(123 = x), [:x], [], [], [])
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
Lists and structs
testee(:(1:3), [], [], [:(:)], [])
testee(:(a[1:3, 4]), [:a], [], [:(:)], [])
testee(:(a[b]), [:a, :b], [], [], [])
testee(:([a[1:3, 4]; b[5]]), [:b, :a], [], [:(:)], [])
testee(:(a.someproperty), [:a], [], [], [])
testee(:([a..., b]), [:a, :b], [], [], [])
testee(:(struct a
b
c
end), [], [:a], [], [:a => ([], [], [], [])])
testee(:(let struct a
b
c
end
end), [], [:a], [], [:a => ([], [], [], [])])
testee(:(module a
f(x) = begin
x
end
z = r
end), [], [:a], [], [])
Types
testee(:(x::Foo = 3), [:Foo], [:x], [], [])
testee(:(x::Foo), [:x, :Foo], [], [], [])
testee(:((a::Foo, b::String = 1, "2")), [:Foo, :String], [:a, :b], [], [])
testee(:(Foo[]), [:Foo], [], [], [])
testee(:(x isa Foo), [:x, :Foo], [], [:isa], [])
testee(:(A{B} = B), [], [:A], [], [])
testee(:(A{T} = Union{T, Int}), [:Int, :Union], [:A], [], [])
testee(:(abstract type a end), [], [:a], [], [:a => ([], [], [], [])])
testee(:(abstract type a <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
testee(:(abstract type a <: b{C} end), [], [:a], [], [:a => ([:b, :C], [], [], [])])
testee(:(abstract type a{T} end), [], [:a], [], [:a => ([], [], [], [])])
testee(:(abstract type a{T, S} end), [], [:a], [], [:a => ([], [], [], [])])
testee(:(abstract type a{T} <: b end), [], [:a], [], [:a => ([:b], [], [], [])])
testee(:(abstract type a{T} <: b{T} end), [], [:a], [], [:a => ([:b], [], [], [])])
UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
:(struct a
#= /home/runner/work/disorganised-mess/disorganised-mess/ExpressionExplorer.pluto.jl#==#7e4cf861-f9f7-46b4-a7d2-36575386259c:1 =#
end)
testee(e, [], [:a], [], [:a => ([], [], [], [])])
testee(:(struct a <: b
c
d::Foo
end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
testee(:(struct a{T, S}
c::T
d::Foo
end), [], [:a], [], [:a => ([:Foo], [], [], [])])
testee(:(struct a{T} <: b
c
d::Foo
end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
testee(:(struct a{T} <: b{T}
c
d::Foo
end), [], [:a], [], [:a => ([:b, :Foo], [], [], [])])
testee(:(struct a
c
a(x = y) = begin
new(x, z)
end
end), [], [:a], [], [:a => ([:y, :z], [], [:new], [])])
Assignment operator & modifiers
testee(:(a = a), [:a], [:a], [], [])
testee(:(a = a + 1), [:a], [:a], [:+], [])
testee(:(x = (a = a + 1)), [:a], [:a, :x], [:+], [])
testee(:(const a = b), [:b], [:a], [], [])
testee(:(f(x) = begin
x
end), [], [], [], [:f => ([], [], [], [])])
testee(:(a[b, c, :] = d), [:a, :b, :c, :d, :(:)], [], [], [])
testee(:(a.b = c), [:a, :c], [], [], [])
testee(:(f(a, b = c, d = e; f = g)), [:a, :c, :e, :g], [], [:f], [])
testee(:(a += 1), [:a], [:a], [:+], [])
testee(:(a >>>= 1), [:a], [:a], [:>>>], [])
testee(:(a ⊻= 1), [:a], [:a], [:⊻], [])
testee(:(a[1] += 1), [:a], [], [:+], [])
testee(:(x = let a = 1
a += b
end), [:b], [:x], [:+], [])
testee(:(_ = a + 1), [:a], [], [:+], [])
testee(:(a = _ + 1), [], [:a], [:+], [])
Tuples
testee(:((a, b)), [:a, :b], [], [], [])
testee(:((a = b, c = 2, d = 123)), [:b], [], [], [])
testee(:((a = b,)), [:b], [], [], [])
testee(:((a, b = 1, 2)), [], [:a, :b], [], [])
testee(:((a, _, c, __ = 1, 2, 3, _d)), [:_d], [:a, :c], [], [])
testee(:(const (a, b) = (1, 2)), [], [:a, :b], [], [])
testee(:(((a, b) = 1, 2)), [], [:a, :b], [], [])
testee(:((a = b, c)), [:b, :c], [:a], [], [])
testee(:((a, b = c)), [:c], [:a, :b], [], [])
testee(:(a = (b, c)), [:b, :c], [:a], [], [])
testee(:((a, (b, c) = [e, [f, g]])), [:e, :f, :g], [:a, :b, :c], [], [])
testee(:(((x, y), a, (b, c) = z, e, (f, g))), [:z, :e, :f, :g], [:x, :y, :a, :b, :c], [], [])
testee(:(((x[i], y.r), a, (b, c) = z, e, (f, g))), [:x, :i, :y, :z, :e, :f, :g], [:a, :b, :c], [], [])
testee(:((a[i], b.r) = (c.d, 2)), [:a, :b, :i, :c], [], [], [])
Broadcasting
testee(:(a .= b), [:b, :a], [], [], [])
testee(:(a .+= b), [:b, :a], [], [:+], [])
testee(:(a[i] .+= b), [:b, :a, :i], [], [:+], [])
testee(:(a .+ b ./ sqrt.(c, d)), [:a, :b, :c, :d], [], [:+, :/, :sqrt], [])
for
& while
testee(:(for k = 1:n
k + s
end), [:n, :s], [], [:+, :(:)], [])
testee(:(for k = 1:2, r = 3:4
global z = k + r
end), [], [:z], [:+, :(:)], [])
testee(:(while k < 2
r = w
global z = k + r
end), [:k, :w], [:z], [:+, :<], [])
try
& catch
testee(:(try
a = b + 1
catch
end), [:b], [], [:+], [])
testee(:(try
a()
catch e
e
end), [], [], [:a], [])
testee(:(try
a()
catch
e
end), [:e], [], [:a], [])
testee(:(try
a + 1
catch a
a
end), [:a], [], [:+], [])
testee(:(try
1
catch e
e
finally
a
end), [:a], [], [], [])
testee(:(try
1
finally
a
end), [:a], [], [], [])
Comprehensions
testee(:([sqrt(s) for s = 1:n]), [:n], [], [:sqrt, :(:)], [])
testee(:([sqrt(s + r) for s = 1:n, r = k]), [:n, :k], [], [:sqrt, :(:), :+], [])
testee(:([s + j + r + m for s = 1:3 for j = 4:5 for (r, l) = [(1, 2)]]), [:m], [], [:+, :(:)], [])
testee(:([a for a = b if a != 2]), [:b], [], [:!=], [])
testee(:([a for a = f() if g(a)]), [], [], [:f, :g], [])
testee(:([c(a) for a = f() if g(a)]), [], [], [:c, :f, :g], [])
testee(:([a for a = a]), [:a], [], [], [])
testee(:(for a = a
a
end), [:a], [], [], [])
testee(:(let a = a
a
end), [:a], [], [], [])
testee(:(let a = a
end), [:a], [], [], [])
testee(:(let a = b
end), [:b], [], [], [])
testee(:(a = a), [:a], [:a], [], [])
testee(:(a = [a for a = a]), [:a], [:a], [], [])
Multiple expressions
testee(:(x = let r = 1
r + r
end), [], [:x], [:+], [])
testee(quote
let r = 1
r + r
end
r = 2
end, [], [:r], [:+], [])
testee(quote
k = 2
123
end, [], [:k], [], [])
testee(quote
a = 1
b = a + 1
end, [], [:a, :b], [:+], [])
testee(Meta.parse("a = 1; b = a + 1"), [], [:a, :b], [:+], [])
testee(:(a = (b = 1)), [], [:a, :b], [], [])
testee(:(let k = 2
123
end), [], [], [], [])
testee(:(let k() = begin
2
end
end), [], [], [], [])
Functions
testee(:(function g()
r = 2
r
end), [], [], [], [:g => ([], [], [], [])])
testee(:(function g end), [], [], [], [:g => ([], [], [], [])])
testee(:(function f()
g(x) = begin
x
end
end), [], [], [], [:f => ([], [], [], [])])
testee(:(function f(x, y = 1; r, s = 3 + 3)
r + s + x * y * z
end), [], [], [], [:f => ([:z], [], [:+, :*], [])])
testee(:(function f(x)
x * y * z
end), [], [], [], [:f => ([:y, :z], [], [:*], [])])
testee(:(function f(x)
x = x / 3
x
end), [], [], [], [:f => ([], [], [:/], [])])
testee(quote
function f(x)
a
end
function f(x, y)
b
end
end, [], [], [], [:f => ([:a, :b], [], [], [])])
testee(:(function f(x, args...; kwargs...)
return [x, y, args..., kwargs...]
end), [], [], [], [:f => ([:y], [], [], [])])
testee(:(function f(x; y = x)
y + x
end), [], [], [], [:f => ([], [], [:+], [])])
testee(:(function (A::MyType)(x; y = x)
y + x
end), [], [], [], [:MyType => ([], [], [:+], [])])
testee(:(f(x, y = a + 1) = begin
x * y * z
end), [], [], [], [:f => ([:z, :a], [], [:*, :+], [])])
testee(quote
f() = begin
1
end
f
end, [], [], [], [:f => ([], [], [], [])])
testee(quote
f() = begin
1
end
f()
end, [], [], [], [:f => ([], [], [], [])])
testee(quote
f(x) = begin
global a = √b
end
f(x, y) = begin
global c = -d
end
end, [], [], [], [:f => ([:b, :d], [:a, :c], [:√, :-], [])])
testee(:(Base.show() = begin
0
end), [:Base], [], [], [[:Base, :show] => ([], [], [], [])])
testee(:((begin
x
p
end->begin
f(x + p)
end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
testee(:((begin
x
p
end->begin
f(x + p)
end)), [], [], [], [:anon => ([], [], [:f, :+], [])])
testee(:(minimum(x) do (a, b)
a + b
end), [:x], [], [:minimum], [:anon => ([], [], [:+], [])])
testee(:(f = (x->begin
x * y
end)), [], [:f], [], [:anon => ([:y], [], [:*], [])])
testee(:(f = ((x, y)->begin
x * y
end)), [], [:f], [], [:anon => ([], [], [:*], [])])
testee(:(f = ((x, y = a + 1)->begin
x * y
end)), [], [:f], [], [:anon => ([:a], [], [:*, :+], [])])
testee(:(((((a, b), c), (d, e))->begin
a * b * c * d * e * f
end)), [], [], [], [:anon => ([:f], [], [:*], [])])
testee(:((a...->begin
f(a...)
end)), [], [], [], [:anon => ([], [], [:f], [])])
testee(:(f = (args...->begin
[args..., y]
end)), [], [:f], [], [:anon => ([:y], [], [], [])])
testee(:(f = ((x, args...; kwargs...)->begin
[x, y, args..., kwargs...]
end)), [], [:f], [], [:anon => ([:y], [], [], [])])
testee(:(f = function (a, b)
a + b * n
end), [:n], [:f], [:+, :*], [])
testee(:(f = function ()
a + b
end), [:a, :b], [:f], [:+], [])
testee(:(func(a)), [:a], [], [:func], [])
testee(:(func(a; b = c)), [:a, :c], [], [:func], [])
testee(:(func(a, b = c)), [:a, :c], [], [:func], [])
testee(:(√b), [:b], [], [:√], [])
testee(:((funcs[i])(b)), [:funcs, :i, :b], [], [], [])
testee(:((f(a))(b)), [:a, :b], [], [:f], [])
testee(:((f(a)).b()), [:a], [], [:f], [])
testee(:(a.b(c)), [:a, :c], [], [[:a, :b]], [])
testee(:(a.b.c(d)), [:a, :d], [], [[:a, :b, :c]], [])
testee(:((a.b(c))(d)), [:a, :c, :d], [], [[:a, :b]], [])
testee(:((a.b(c)).d(e)), [:a, :c, :e], [], [[:a, :b]], [])
testee(:((a.b[c]).d(e)), [:a, :c, :e], [], [], [])
Functions & types
testee(:(function f(y::Int64 = a)::String
string(y)
end), [], [], [], [:f => ([:String, :Int64, :a], [], [:string], [])])
testee(quote
f(a::A)::C = begin
a.a
end
end, [], [], [], [:f => ([:A, :C], [], [], [])])
testee(:(function f(x::T; k = 1) where T
return x + 1
end), [], [], [], [:f => ([], [], [:+], [])])
testee(:(function f(x::T; k = 1) where {T, S <: R}
return x + 1
end), [], [], [], [:f => ([:R], [], [:+], [])])
testee(:(f(x)::String = begin
x
end), [], [], [], [:f => ([:String], [], [], [])])
testee(:(Symbol("@MIME_str")], [])
()), [], [], [testee(:(function f(:: ())
1
end), [], [], [], [:f => ([], [], [Symbol("@MIME_str")], [])])
testee(:((a(a::AbstractArray{T}) where T) = begin
5
end), [], [], [], [:a => ([:AbstractArray], [], [], [])])
testee(:((a(a::AbstractArray{T, R}) where {T, S}) = begin
a + b
end), [], [], [], [:a => ([:AbstractArray, :b, :R], [], [:+], [])])
testee(:(f(::A) = begin
1
end), [], [], [], [:f => ([:A], [], [], [])])
testee(:(f(::A, ::B) = begin
1
end), [], [], [], [:f => ([:A, :B], [], [], [])])
testee(:(f(a::A, ::B, c::C...) = begin
a + c
end), [], [], [], [:f => ([:A, :B, :C], [], [:+], [])])
testee(:((obj::MyType)(x, y) = begin
x + z
end), [], [], [], [:MyType => ([:z], [], [:+], [])])
testee(:((obj::MyType)() = begin
1
end), [], [], [], [:MyType => ([], [], [], [])])
testee(:((obj::MyType)(x, args...; kwargs...) = begin
[x, y, args..., kwargs...]
end), [], [], [], [:MyType => ([:y], [], [], [])])
testee(:(function (obj::MyType)(x, y)
x + z
end), [], [], [], [:MyType => ([:z], [], [:+], [])])
testee(quote
struct MyType
x::String
end
(obj::MyType)(y) = begin
obj.x + y
end
end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
testee(quote
struct MyType
x::String
end
function (obj::MyType)(y)
obj.x + y
end
end, [], [:MyType], [], [:MyType => ([:String], [], [:+], [])])
testee(:((::MyType)(x, y) = begin
x + y
end), [], [], [], [:MyType => ([], [], [:+], [])])
Scope modifiers
testee(:(let global (a, b) = (1, 2)
end), [], [:a, :b], [], [])
UndefVarError: @test_broken not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
testee(:(let global k = 3
end), [], [:k], [], [])
UndefVarError: @test_broken not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
testee(:(let global k = 3
k
end), [], [:k], [], [])
testee(:(let global k += 3
end), [:k], [:k], [:+], [])
testee(:(let global k
k = 4
end), [], [:k], [], [])
testee(:(let global k
b = 5
end), [], [], [], [])
testee(:(let a = 1, b = 2
show(a + b)
end), [], [], [:show, :+], [])
testee(quote
local (a, b) = (1, 2)
end, [], [], [], [])
testee(quote
local a = (b = 1)
end, [], [:b], [], [])
testee(quote
local k = 3
end, [], [], [], [])
testee(quote
local k = r
end, [:r], [], [], [])
testee(quote
local k = 3
k
b = 4
end, [], [:b], [], [])
testee(quote
local k += 3
end, [], [], [:+], [])
testee(quote
local k
k = 4
end, [], [], [], [])
testee(quote
local k
b = 5
end, [], [:b], [], [])
testee(quote
local r[1] = 5
end, [:r], [], [], [])
UndefVarError: @test_broken not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
testee(:(function f(x)
global k = x
end), [], [], [], [:f => ([], [:k], [], [])])
testee(:((begin
x = 1
end, y)), [:y], [:x], [], [])
testee(:(x = let global a += 1
end), [:a], [:x, :a], [:+], [])
import
& using
testee(:(using Plots), [], [:Plots], [], [])
testee(:(using Plots.ExpressionExplorer), [], [:ExpressionExplorer], [], [])
testee(:(using JSON, UUIDs), [], [:JSON, :UUIDs], [], [])
testee(:(import Pluto), [], [:Pluto], [], [])
testee(:(import Pluto: wow, wowie), [], [:wow, :wowie], [], [])
testee(:(import Pluto.ExpressionExplorer.wow, Plutowie), [], [:wow, :Plutowie], [], [])
testee(:(import .Pluto: wow), [], [:wow], [], [])
testee(:(import ..Pluto: wow), [], [:wow], [], [])
testee(:(let
import Pluto.wow, Dates
end), [], [:wow, :Dates], [], [])
testee(:(while false
import Pluto.wow, Dates
end), [], [:wow, :Dates], [], [])
testee(:(try
using Pluto.wow, Dates
catch
end), [], [:wow, :Dates], [], [])
testee(:(module A
import B
end), [], [:A], [], [])
Foreign macros
parameterizedfunctions
testee(quote
f = (begin
dx = a * x - b * x * y
dy = -c * y + d * x * y
end, a, b, c, d)
end, [], [:f, :LotkaVolterra], [Symbol("@ode_def")], [])
testee(quote
f = (a, b, c, d)
end, [], [:f], [Symbol("@ode_def")], [])
Flux
testee(:(Symbol("@functor")], [])
()), [], [:Asdf], [Symbolics.jl
testee(:(Symbol("@variables")], [])
(b, c)), [], [:a, :b, :c], [testee(:(Symbol("@variables")], [])
()), [], [:a, :b, :c], [testee(:(1:2], c(t), d(..))), [], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
(b[testee(:(1:x], (c[1:10])(t), d(..))), [:x], [:a, :b, :c, :d, :t], [:(:), Symbol("@variables")], [])
(b[testee(:(Symbol("@variables")], [])
()), [:x], [:a, :b, :c, :d, :t], [:(:), UndefVarError: @test_nowarn not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
JuMP
Macros
testee(:(Symbol("@time")], [])
()), [], [:a], [testee(:(Symbol("@f")], [])
(x)), [:x, :z], [], [testee(:(Symbol("@f")], [])
(y = z)), [:x, :z], [], [testee(:(Base.Symbol("@time")]], [])
()), [:Base], [:a], [[:Base, testee(:(Symbol("@gensym")], [])
(b, c)), [], [:a, :b, :c], [:gensym, testee(:(Base.Symbol("@gensym")]], [])
(b, c)), [:Base], [:a, :b, :c], [:gensym, [:Base, testee(:(Base.Symbol("@kwdef")], [:Base, Symbol("@__doc__")]], [:A => ([:Int, :two], [], [], [])])
()), [:Base], [:A], [[:Base, testee(quote
Core. f(x) = begin
x
end
end, [], [], [Symbol("@doc")], [:f => ([], [], [], [])])
testee(:(Symbol("@bind")], [])
(b)), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond],
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}())
testee(:(PlutoRunner.Symbol("@bind")]], [])
(b)), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], [:PlutoRunner,
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}())
UndefVarError: @test_broken not defined
Here is what happened, the most recent locations are first:
- from :0
- #macroexpand#51from expr.jl:115
- macroexpandfrom expr.jl:114
testee(:(let (b)
end), [:b, :PlutoRunner, :Base, :Core], [:a], [[:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], Symbol("@bind")], [])
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}())
testee(:(Symbol("@asdf")], [])
(b = x2, c = x3)), [:x1, :x2, :x3], [:a], [testee(:(Float64], [:a], [[Symbol("@einsum")], [:*]], [])
()), [:x, :y, :testee(:(Symbol("@tullio")], [:f], [:*], [:+]], [])
(init = z)), [:x, :k, :z], [:a], [[testee(:(Pack.Float64], [:a], [[:Pack, Symbol("@asdf")], [:/], [:log]], [])
()), [:x, :y, :k, :Pack, :Failed to show value:
BoundsError: attempt to access 2-element Vector{Any} at index [3]
Here is what happened, the most recent locations are first:
- getindexfrom array.jl:861
- show_unquoted
(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64) from show.jl:2084 - show_unquoted
(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64) from show.jl:2199 - show_list
(io::IOContext{IOBuffer}, items::Vector{Any}, sep::String, indent::Int64, prec::Int64, quote_level::Int64, enclose_operators::Bool, kw::Bool) from show.jl:1556 - show_enclosed_listfrom show.jl:1565
- show_call
(io::IOContext{IOBuffer}, head::Symbol, func::Symbol, func_args::Vector{Any}, indent::Int64, quote_level::Int64, kw::Bool) from show.jl:1601 - show_unquoted
(io::IOContext{IOBuffer}, ex::Expr, indent::Int64, prec::Int64, quote_level::Int64) from show.jl:1944 - show_unquotedfrom show.jl:1778
- print
(io::IOBuffer, ex::Expr) from show.jl:1303 - print_to_string
(::String, ::Vararg{Any}) from io.jl:144 - string
(::String, ::Expr, ::Vararg{Any}) from io.jl:185 - show
(io::IOContext{IOBuffer}, mime::MIME{Symbol("text/html")}, value::Main.workspace#2.Pass) from Other cell: line 2function Base.show(io::IO, mime::MIME"text/html", value::Pass)
show(io, mime, HTML("""
<div
style="
testee(:(Symbol("@md_str"), Symbol("@bind")], [])
()), [:b, :PlutoRunner, :Base, :Core], [:a], [:getindex, [:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], 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}())
testee(:(Symbol("@md_str"), Symbol("@bind")], [])
()), [:b, :a, :PlutoRunner, :Base, :Core], [:a], [:getindex, [:Base, :get], [:Core, :applicable], [:PlutoRunner, :Bond], 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}())
testee(:(Symbol("@html_str")], [])
()), [], [], [testee(:(Symbol("@md_str")], [])
()), [:c], [:b], [:getindex, testee(:(Symbol("@md_str")], [])
()), [:r], [], [:getindex, testee(:(Symbol("@md_str")], [])
()), [], [], [:getindex, testee(:(macro a()
end), [], [], [], [Symbol("@a") => ([], [], [], [])])
testee(:(macro a(b::Int)
b
end), [], [], [], [Symbol("@a") => ([:Int], [], [], [])])
testee(:(macro a(b::Int = c)
end), [], [], [], [Symbol("@a") => ([:Int, :c], [], [], [])])
testee(:(macro a()
b = c
return b
end), [], [], [], [Symbol("@a") => ([:c], [], [], [])])
String interpolation & expressions
testee(:("a $(b)"), [:b], [], [], [])
testee(:("a $(b = c)"), [:c], [:b], [], [])
testee(:(ex = :yayo), [], [:ex], [], [])
testee(:(ex = :(yayo + $r)), [], [:ex], [], [])
Extracting using
and import
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
:(using .C: r)
:(using A)
:(import B)
:(import ..D.E: f, g)
:(import H.I, J, K.L)
using_test_result.usings == Set{Expr}([:(using A), :(using .C: r)])
using_test_result.imports == Set{Expr}([:(import B), :(import ..D.E: f, g), :(import H.I, J, K.L)])
external_package_names(using_test_result) == Set{Symbol}([:A, :B, :H, :J, :K])
external_package_names(:(using Plots, Something.Else, .LocalModule)) == Set([:Plots, :Something])
external_package_names(:(import Plots.A: b, c)) == Set([:Plots])
Appendix
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
easy_symstate (generic function with 1 method)
Visual testing
Any
@test2 (macro with 1 method)
notebook1 == deepcopy(notebook1)
remove_linenums (generic function with 1 method)
remove_linenums (generic function with 2 methods)
DisplayOnly
skip_as_script (generic function with 1 method)
@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.
The opposite of @skip_as_script
2
1 + 1 == x
1 + 1 + 1 == x
throw("Oh my god") == x
Track
sleep(0.1)
Tracked
@track (macro with 1 method)
prettytime (generic function with 1 method)
Table of contents
"@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"
#1 (generic function with 1 method)