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 20 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

PlutoTest.jl

👀 Reading hidden code
176 μs

This notebook introduces visual testing:

👀 Reading hidden code
239 μs
sqrt(20 - 11) == 3 sqrt(
9
) == 3
3.0
== 3
true
👀 Reading hidden code
@test sqrt(20-11) == 3
12.5 ms
iseven(123 + 7 ^ 3) iseven(123 +
343
)
iseven(
466
)
true
👀 Reading hidden code
@test iseven(123 + 7^3)
5.9 ms
4 + 4 ∈ [1:7...]
8
∈ [1:7...]
8
false
👀 Reading hidden code
@test 4+4 ∈ [1:7...]
35.3 ms
👀 Reading hidden code
67.8 μs
👀 Reading hidden code
66.9 μs
is_good_boy(first(friends)) is_good_boy(first(
))
is_good_boy(
)
true
👀 Reading hidden code
36.0 ms

Tests have time-travel functionality built in! Click on the tests above.

👀 Reading hidden code
300 μs

Time travel

👀 Reading hidden code
174 μs

This notebook contains visual debugging:

👀 Reading hidden code
219 μs
Error message

UndefVarError: plot not defined

Stack trace

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

  1. eval
  2. 	results = Any[]		seval(ex) = Computed(Core.eval(m,ex))		# will be modified
  3. Show more...
Oh no! 🙀
@visual_debug begin
(1+2) + (7-6)
plot(2000 .+ 30 .* rand(2+2))
4+5
sqrt(sqrt(sqrt(5)))
md"# Wow"
end
👀 Reading hidden code
---
👀 Reading hidden code
16.3 μs
👀 Reading hidden code
192 ms
👀 Reading hidden code
81.6 ms
👀 Reading hidden code
328 μs

(You need Pluto#main to run this notebook)

👀 Reading hidden code
171 μs
toc()
👀 Reading hidden code
11.2 ms
x = [1,3]
👀 Reading hidden code
13.0 μs
x == [1, 2 + 2]
== [1, 2 + 2]
==
false
@test x == [1,2+2]
👀 Reading hidden code
21.1 ms
missing == 2
missing
== 2
missing
@test missing == 2
👀 Reading hidden code
66.5 ms
2 + 2 == 2 + 2
4
== 2 + 2
4
==
4
true
@test 2+2 == 2+2
👀 Reading hidden code
13.0 ms
rand(50) == [rand(50), 2]
== [rand(50), 2]
==
false
@test rand(50) == [rand(50),2]
👀 Reading hidden code
73.9 ms
always_false(rand(howmuch), rand(howmuch), 123) always_false(rand(
0
), rand(howmuch), 123)
always_false(
, rand(howmuch), 123)
always_false(
, rand(
0
), 123)
always_false(
,
, 123)
true
@test always_false(rand(howmuch), rand(howmuch),123)
👀 Reading hidden code
3.1 ms
@bind howmuch Slider(0:100)
👀 Reading hidden code
146 ms
always_false(rand(2), rand(2), 123) always_false(
, rand(2), 123)
always_false(
,
, 123)
true
@test always_false(rand(2), rand(2),123)
👀 Reading hidden code
589 μs
!(!(always_false(rand(2), rand(2), 123; r = 123)))
true
@test !!always_false(rand(2), rand(2),123; r=123)
👀 Reading hidden code
30.9 ms
always_false([1, 2, 3]...)
true
@test always_false([1,2,3]...)
👀 Reading hidden code
3.2 ms
isless(2 + 2, 1) isless(
4
, 1)
false
@test isless(2+2,1)
👀 Reading hidden code
3.4 ms
isless(1, 2 + 2) isless(1,
4
)
true
@test isless(1,2+2)
👀 Reading hidden code
456 μs
@bind n Slider(1:10)
👀 Reading hidden code
692 μs
iseven(n ^ 2) iseven(
1
^ 2)
iseven(
1
)
false
@test iseven(n^2)
👀 Reading hidden code
3.1 ms
@bind k Slider(0:15)
👀 Reading hidden code
571 μs
4 + 4 ∈ [1:k...]
8
∈ [1:k...]
8
false
@test 4+4 ∈ [1:k...]
👀 Reading hidden code
12.0 ms
isempty((1:k) .^ 2) isempty((1:
0
) .^ 2)
isempty(
1:0
.^ 2)
isempty(
)
true
@test isempty((1:k) .^ 2)
👀 Reading hidden code
69.9 ms
map(1:10) do i
@test sqrt($i) < 3 && always_false()
end
👀 Reading hidden code
110 ms
isempty([1, sqrt(2)]) isempty(
)
false
@test isempty([1,sqrt(2)])
👀 Reading hidden code
2.9 ms
1 ∈ [sqrt(20), 5:9...] 1 ∈
false
👀 Reading hidden code
34.9 ms
1 ∈ rand(60) 1 ∈
false
👀 Reading hidden code
434 μs
rand(60) ∋ 1
∋ 1
false
👀 Reading hidden code
7.9 ms
always_false (generic function with 1 method)
always_false(args...; kwargs...) = true
👀 Reading hidden code
1.1 ms
"pt-dot {\n\tflex: 0 0 auto;\n\tbackground: grey;\n\twidth: 1em;\n\theight: 1em;\n\tbottom: -.1em;\n\tborder-radius: 100%;\n\tmargin-right: .7em;\n\tdisplay: block;\n\tposition: relative;\n\tcursor: pointer;\n}\n\npt-dot.floating {\n\tposition: fixed;\n\tz-index: 60;\n\tvisibility: hidden;\n\ttransition: transform linear 120ms;\n" ⋯ 1338 bytes ⋯ "-frame-viewer {\n    max-width: 100%;\n}\n.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like {\n\tflex-wrap: wrap;\n}\n.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like > pluto-display[mime=\"application/vnd.pluto.tree+object\"] {\n\t/*flex-basis: 100%;*/\n}\n"
const pluto_test_css = """
pt-dot {
flex: 0 0 auto;
background: grey;
width: 1em;
height: 1em;
bottom: -.1em;
border-radius: 100%;
margin-right: .7em;
display: block;
position: relative;
cursor: pointer;
}

pt-dot.floating {
position: fixed;
z-index: 60;
visibility: hidden;
transition: transform linear 120ms;
opacity: .8;
}
.show-top-float > pt-dot.floating.top,
.show-bottom-float > pt-dot.floating.bottom {
visibility: visible;
}

pt-dot.floating.top {
top: 5px;
}
pt-dot.floating.bottom {
bottom: 5px;
}


.fail > pt-dot {
background: #f75d5d;

}
.pass > pt-dot {
background: #56a038;
}

@keyframes fadeout {
0% { opacity: 1;}
100% { opacity: 0; pointer-events: none;}
}


.pass > pt-dot.floating {

animation: fadeout 2s;

animation-fill-mode: both;
animation-delay: 2s;

/*opacity: 0.4;*/
}


.pluto-test {
font-family: "JuliaMono", monospace;
font-size: 0.75rem;
padding: 4px;

min-height: 25px;
}


.pluto-test.pass {
color: rgba(0,0,0,.5);
}

.pluto-test.fail {
background: linear-gradient(90deg, #ff2e2e14, transparent);
border-radius: 7px;
}


.pluto-test>.arg_result {
flex: 0 0 auto;
}

.pluto-test>.arg_result>div,
.pluto-test>.arg_result>div>pluto-display>div {
display: inline-flex;
}


.pluto-test>.comma {
margin-right: .5em;
}

.pluto-test.call>code {
padding: 0px;
}

.pluto-test.call.infix-operator>div {
overflow-x: auto;
}

.pluto-test {
display: flex;
align-items: baseline;
}

.pluto-test.call.infix-operator>.fname {
margin: 0px .6em;
/*color: darkred;*/
}


/* expanding */


.pluto-test:not(.expanded) > p-frame-viewer > p-frame-controls {
display: none;
}

.pluto-test.expanded > p-frame-viewer {
max-width: 100%;
}
.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like {
flex-wrap: wrap;
}
.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like > pluto-display[mime="application/vnd.pluto.tree+object"] {
/*flex-basis: 100%;*/
}
"""
👀 Reading hidden code
95.9 μs
"p-frame-viewer {\n\tdisplay: inline-flex;\n\tflex-direction: column;\n}\np-frames,\np-frame-controls {\n\tdisplay: inline-flex;\n}\n"
const frames_css = """
p-frame-viewer {
display: inline-flex;
flex-direction: column;
}
p-frames,
p-frame-controls {
display: inline-flex;
}
"""
👀 Reading hidden code
97.0 μs
using HypertextLiteral
👀 Reading hidden code
3.7 ms
# using PlutoUI
👀 Reading hidden code
8.6 μs
import Test
👀 Reading hidden code
142 μs
@test_deprecated (macro with 1 method)
begin
export @test_nowarn, @test_warn, @test_logs, @test_skip, @test_broken, @test_throws, @test_deprecated
var"@test_warn" = Test.var"@test_warn"
var"@test_nowarn" = Test.var"@test_nowarn"
var"@test_logs" = Test.var"@test_logs"
var"@test_skip" = Test.var"@test_skip"
var"@test_broken" = Test.var"@test_broken"
var"@test_throws" = Test.var"@test_throws"
var"@test_deprecated" = Test.var"@test_deprecated"
end
👀 Reading hidden code
411 μs

Type definitions

👀 Reading hidden code
196 μs
abstract type TestResult end
👀 Reading hidden code
332 μs
abstract type Fail <: TestResult end
👀 Reading hidden code
322 μs
abstract type Pass <: TestResult end
👀 Reading hidden code
319 μs
Any
const Code = Any
👀 Reading hidden code
107 μs
# struct Correct <: Pass
# expr::Code
# end
👀 Reading hidden code
8.6 μs
struct CorrectCall <: Pass
expr::Code
steps::Vector
end
👀 Reading hidden code
1.2 ms
# struct Error <: Fail
# expr::Code
# error
# end
👀 Reading hidden code
9.0 μs
# struct Wrong <: Fail
# expr::Code
# step
# end
👀 Reading hidden code
9.0 μs
struct WrongCall <: Fail
expr::Code
steps::Vector
end
👀 Reading hidden code
1.2 ms

Test macro

👀 Reading hidden code
189 μs
map(1:15) do i
@test 2 * $i > 0.19
end
👀 Reading hidden code
30.3 ms
step_by_step (generic function with 1 method)
function step_by_step(expr; __module__)
Computed
onestep_light
if can_interpret(expr)
quote
Any[$(QuoteNode(expr)), $(onestep_light)($(esc(Expr(:quote,expr))); m=$(__module__))...]
end
else
quote
[$(QuoteNode(expr)), Computed($(esc(expr)))]
end
end
end
👀 Reading hidden code
1.8 ms
test (generic function with 1 method)
function test(expr, extra_args...; __module__)
step_by_step
Test.test_expr!("", expr, extra_args...)
quote
expr_raw = $(QuoteNode(expr))
try
# steps = @eval_step_by_step($(expr))
steps = $(step_by_step(expr; __module__=__module__))
# arg_results = [$((expr.args[2:end] .|> esc)...)]
# result = $(esc(:eval))(Expr(:call, $(expr.args[1] |> QuoteNode), arg_results...))
result = unwrap_computed(last(steps))
if result === true
CorrectCall(expr_raw, steps)
# elseif result === false
# WrongCall(expr_raw, steps)
else
WrongCall(expr_raw, steps)
end
catch e
rethrow(e)
# Error(expr_raw, e)
end
end
end
👀 Reading hidden code
2.1 ms
👀 Reading hidden code
8.4 μs
begin
    var"#715#expr_raw" = $(QuoteNode(:(x == [1, 2 + i])))
    try
        var"#716#steps" = begin
                Main.workspace#6.Any[$(QuoteNode(:(x == [1, 2 + i]))), (Main.workspace#3.onestep_light)($(Expr(:copyast, :($(QuoteNode(:(x == [1, 2 + i])))))); m = Main.workspace#7)...]
            end
        var"#717#result" = Main.workspace#6.unwrap_computed(Main.workspace#6.last(var"#716#steps"))
        if var"#717#result" === true
            Main.workspace#6.CorrectCall(var"#715#expr_raw", var"#716#steps")
        else
            Main.workspace#6.WrongCall(var"#715#expr_raw", var"#716#steps")
        end
    catch var"#720#e"
        Main.workspace#6.rethrow(var"#720#e")
    end
end
var"@test"; macroexpand(@__MODULE__, :(@test x == [1,2+i]); recursive=false) |> prettycolors
👀 Reading hidden code
30.4 ms
always_false(rand(20), rand(20), 123) always_false(
, rand(20), 123)
always_false(
,
, 123)
true
t = @test always_false(rand(20), rand(20),123)
👀 Reading hidden code
557 μs
begin (1 + 2) - (3 + 4) falseend begin
3
- (3 + 4)
falseend
begin
3
-
7
falseend
begin
-4
falseend
false
@test begin
(1+2) - (3+4)
false
end
👀 Reading hidden code
2.8 ms
#= /home/runner/work/disorganised-mess/disorganised-mess/testing and debugging 3.jl#==#176f39f1-fa36-4ce1-86ba-76248848a834:1 =# @test(always_false(rand(30), 123)) isa Fail
always_false(rand(30), 123) always_false(
, 123)
true
isa Fail
always_false(rand(30), 123) always_false(
, 123)
true
isa
Fail
false
@test (@test always_false(rand(30),123)) isa Fail
👀 Reading hidden code
36.2 ms
false
false
embed_display(@test false)
👀 Reading hidden code
19.9 ms
always_false("asd" * "asd", "asd", "asd" * " asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd") always_false(
"asdasd"
, "asd", "asd" * " asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd",
"asdasd"
, "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd", "asd" * "asd", "asd", "asd" * "asd", "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd", "asd" * "asd", "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd", "asd" * "asd", "asd")
always_false(
"asdasd"
, "asd",
"asd asd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd",
"asdasd"
, "asd")
true
@test always_false("asd"*"asd","asd","asd"*" asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd")
👀 Reading hidden code
19.0 ms
("asd"*"asd","asd","asd"*" asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd","asd"*"asd","asd")
👀 Reading hidden code
23.3 μs
# @test (@test t isa Pass) isa Pass
👀 Reading hidden code
9.0 μs
≈(π, 3.14, atol = 0.01, rtol = 1)
true
@test π ≈ 3.14 atol=0.01 rtol=1
👀 Reading hidden code
26.4 ms
embed_display (generic function with 1 method)
embed_display = if isdefined(Main, :PlutoRunner) && isdefined(Main.PlutoRunner, :embed_display)
# if this package is used inside Pluto, and Pluto is new enough
Main.PlutoRunner.embed_display
else
identity
end
👀 Reading hidden code
36.7 μs
begin
export @test
macro test(expr...)
test(expr...; __module__=__module__)
end
function Base.show(io::IO, m::MIME"text/html", call::Union{WrongCall,CorrectCall})

infix = if Meta.isexpr(call.expr, :call)
fname = call.expr.args[1]
Meta.isbinaryoperator(fname)
else
false
end
classes = [
"pluto-test",
"call",
(isa(call,CorrectCall) ? "correct" : "wrong"),
(isa(call,Pass) ? "pass" : "fail"),
infix ? "infix-operator" : "prefix-operator",
]
result = @htl("""
<div class=$(classes)>
<script>
const div = currentScript.parentElement
div.addEventListener("click", (e) => {
if(!div.classList.contains("expanded") || e.target.closest("pt-dot:not(.floating)") != null){
div.classList.toggle("expanded")
e.stopPropagation()
}
})
const throttled = (f, delay) => {
const waiting = { current: false }
return () => {
if (!waiting.current) {
f()
waiting.current = true
setTimeout(() => {
f()
waiting.current = false
}, delay)
}
}
}
const dot = div.querySelector("pt-dot")
const dot_top = div.querySelector("pt-dot.top")
const dot_bot = div.querySelector("pt-dot.bottom")

const intersect = (r) => {
const topdistance = r.top
const botdistance = window.visualViewport.height - r.bottom
const t = (x) => `translate(\${2*Math.sqrt(Math.max(0,-50-x))}px,0)`
dot_top.style.transform = t(topdistance)
dot_bot.style.transform = t(botdistance)

div.classList.toggle("show-top-float", topdistance < 4)
div.classList.toggle("show-bottom-float", botdistance < 4)
}
intersect(dot.getBoundingClientRect())
window.addEventListener("scroll", throttled(() => {
intersect(dot.getBoundingClientRect())
}, 100))

let observer = new IntersectionObserver((es) => {
const e = es[0]
intersect(e.boundingClientRect)
}, {
rootMargin: '-4px',
threshold: 1.0
});

observer.observe(dot)
invalidation.then(() => {
observer.unobserve(dot)
})
Array.from(div.querySelectorAll("pt-dot.floating")).forEach(e => {
e.addEventListener("click", () => dot.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"}))
})
</script>
<pt-dot></pt-dot>
<pt-dot class="floating top"></pt-dot>
<pt-dot class="floating bottom"></pt-dot>
$(frames(SlottedDisplay.( call.steps)))
</div>
<style>
$(pluto_test_css)
$(slotted_code_css)
</style>
""")
Base.show(io, m, result)
end
end
👀 Reading hidden code
2.0 ms
flatmap (generic function with 1 method)
flatmap(args...) = vcat(map(args...)...)
👀 Reading hidden code
430 μs
embed_display (generic function with 1 method)
emb = embed_display
👀 Reading hidden code
10.9 μs
div (generic function with 1 method)
div(x; class="", style="") = @htl("<div class=$(class) style=$(style)>$(x)</div>")
👀 Reading hidden code
1.6 ms
div (generic function with 2 methods)
div(; class="", style="") = x -> @htl("<div class=$(class) style=$(style)>$(x)</div>")
👀 Reading hidden code
1.9 ms
"x + 1"
expr_to_str(:(x+1))
👀 Reading hidden code
38.7 ms
expr_to_str (generic function with 2 methods)
expr_to_str(e, mod=@__MODULE__()) = let
Computed
sprint() do io
Base.print(IOContext(io, :module => @__MODULE__), remove_linenums(e))
end
end
👀 Reading hidden code
1.2 ms
prettycolors (generic function with 1 method)
prettycolors(e) = Markdown.MD([Markdown.Code("julia", expr_to_str(e))])
👀 Reading hidden code
478 μs
remove_linenums (generic function with 1 method)
remove_linenums(e::Expr) = if e.head === :macrocall
Expr(e.head, (remove_linenums(x) for x in e.args)...)
else
Expr(e.head, (remove_linenums(x) for x in e.args if !(x isa LineNumberNode))...)
end
👀 Reading hidden code
1.9 ms
remove_linenums (generic function with 2 methods)
remove_linenums(x) = x
👀 Reading hidden code
346 μs
UInt64
👀 Reading hidden code
2.2 ms
expr_hash (generic function with 1 method)
👀 Reading hidden code
1.1 ms
expr_hash (generic function with 2 methods)
👀 Reading hidden code
369 μs

Time travel evaluation

In Julia, expressions are objects! This means that, before evaluation, code is expressed as a Julia object:

👀 Reading hidden code
476 μs
:(first([56, sqrt(9)]))
ex1 = :(first([56,sqrt(9)]))
👀 Reading hidden code
22.9 μs
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol first
    2: Expr
      head: Symbol vect
      args: Array{Any}((2,))
        1: Int64 56
        2: Expr
          head: Symbol call
          args: Array{Any}((2,))
            1: Symbol sqrt
            2: Int64 9
Dump(ex1)
👀 Reading hidden code
106 ms

You can use Core.eval to evaluate expressions at runtime:

👀 Reading hidden code
199 μs
56.0
Core.eval(Module(), ex1)
👀 Reading hidden code
27.4 ms

But did you know that you can also partially evaluate expressions?

👀 Reading hidden code
274 μs
:([56, sqrt(9)])
ex2_inner = ex1.args[2]
👀 Reading hidden code
18.5 μs
ex2_inner_result = Core.eval(Module(), ex2_inner)
👀 Reading hidden code
322 μs
:(first([56.0, 3.0]))
ex2 = Expr(:call, :first, ex2_inner_result)
👀 Reading hidden code
20.7 μs
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol first
    2: Array{Float64}((2,)) [56.0, 3.0]
Dump(ex2)
👀 Reading hidden code
7.6 ms

Here, ex2 is not a raw Expr — it contains an evaluated array!

👀 Reading hidden code
247 μs

Computed struct

Our time travel mechanism will be based on the partial evaluation principle introduced above. To differentiate between computed results and the original expression, we will wrap all computed results in a struct.

👀 Reading hidden code
253 μs
struct Computed
x
end
👀 Reading hidden code
829 μs
:(first(Computed([56.0, 3.0])))
ex3 = Expr(:call, :first, Computed(ex2_inner_result))
👀 Reading hidden code
18.3 μs
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol first
    2: Main.workspace#3.Computed
      x: Array{Float64}((2,)) [56.0, 3.0]
Dump(ex3)
👀 Reading hidden code
63.9 μs

We also add a function to recursively unwrap an expression with Computed entries:

👀 Reading hidden code
219 μs
unwrap_computed (generic function with 1 method)
unwrap_computed(x) = x
👀 Reading hidden code
334 μs
unwrap_computed (generic function with 2 methods)
unwrap_computed(c::Computed) = c.x
👀 Reading hidden code
374 μs
unwrap_computed (generic function with 3 methods)
unwrap_computed(e::Expr) = Expr(e.head, unwrap_computed.(e.args)...)
👀 Reading hidden code
485 μs
:(first([56.0, 3.0]))
unwrap_computed(ex3)
👀 Reading hidden code
56.2 ms

Stepping function

👀 Reading hidden code
163 μs
quote
sqrt(sqrt(4)) + 2
end |> onestep_light .|> prettycolors
👀 Reading hidden code
252 ms
onestep_light (generic function with 1 method)
function onestep_light(e::Expr; m=Module())::Vector
results = Any[]
seval(ex) = Computed(Core.eval(m,ex))
# will be modified
arg_results = Any[a for a in e.args]
push_intermediate() = push!(results, Expr(e.head, arg_results...))

# we only step for expressions where this is possible/easy
if e.head === :call || e.head === :begin || e.head === :block# || e.head === :vect

for (i,a) in enumerate(e.args)
if a isa QuoteNode
a
elseif (Meta.isexpr(e, :call) || Meta.isexpr(e, :let)) && i == 1
a
elseif a isa Symbol
arg_results[i] = seval(a)
push_intermediate()
elseif a isa Expr
inner_results = onestep_light(a; m=m)
for ir in inner_results
arg_results[i] = ir
push_intermediate()
end

arg_results[i] = inner_results[end]
else
a
end
end
end
push!(results, seval(unwrap_computed(Expr(e.head, arg_results...))))
results
end
👀 Reading hidden code
5.8 ms
onestep_light (generic function with 2 methods)
onestep_light(x::Any; m=Module()) = [Computed(Core.eval(m,x))]
👀 Reading hidden code
1.3 ms
md"""

"""
👀 Reading hidden code
118 μs
@eval_step_by_step (macro with 1 method)
macro eval_step_by_step(e)
step_by_step(e; __module__=__module__)
end
👀 Reading hidden code
548 μs
quote
    #= /home/runner/work/disorganised-mess/disorganised-mess/testing and debugging 3.jl#==#e1c306e3-0a47-4149-a9fb-ec7ab380fa11:6 =#
    Main.workspace#3.Any[$(QuoteNode(:(x == [1, 2]))), (Main.workspace#3.onestep_light)($(Expr(:copyast, :($(QuoteNode(:(x == [1, 2])))))); m = Main.workspace#4)...]
end
@macroexpand @eval_step_by_step x == [1,2]
👀 Reading hidden code
156 μs
@eval_step_by_step sqrt(sqrt(length([1,2])))
👀 Reading hidden code
37.1 ms
@eval_step_by_step xasdf = 123
👀 Reading hidden code
53.2 ms
Error message

UndefVarError: xasdf not defined

Stack trace

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

  1. xasdf
You got this!
xasdf
👀 Reading hidden code
---
onestep_light(quote
1+2
2+3
4+5
sqrt(sqrt(sqrt(5)))
end) .|> prettycolors
👀 Reading hidden code
19.4 ms
can_interpret (generic function with 1 method)
can_interpret(x) = true
👀 Reading hidden code
333 μs
can_interpret_call_arg (generic function with 1 method)
can_interpret_call_arg(e::Expr) = !(e.head === :(...) || e.head === :kw || e.head === :parameters)
👀 Reading hidden code
630 μs
can_interpret_call_arg (generic function with 2 methods)
can_interpret_call_arg(x) = true
👀 Reading hidden code
335 μs
true
Meta.isbinaryoperator(:(==))
👀 Reading hidden code
5.3 ms
can_interpret (generic function with 2 methods)
can_interpret(e::Expr) = if false
false
elseif e.head === :call && !all(can_interpret_call_arg, e.args)
false
# elseif e.head === :(=) || e.head === :macrocall
# false
else
all(can_interpret, e.args)
end
👀 Reading hidden code
662 μs

Displaying objects inside code

👀 Reading hidden code
166 μs

Slotting

We walk through the expression tree. Whenever we find a Computed object, we generate a random key (e.g. iosjddfo), we add it to our dictionary (found). In the expression, we replace the Computed object with a placeholder symbol __slotiosjddfo__. We will later be able to match the object to this slot.

👀 Reading hidden code
256 μs
slot! (generic function with 1 method)
slot!(found, c::Computed) = let
k = Symbol("__slot", join(rand('a':'z', 16)), "__")
found[k] = c
k
end
👀 Reading hidden code
610 μs
slot! (generic function with 2 methods)
slot!(found, x) = x
👀 Reading hidden code
348 μs
slot! (generic function with 3 methods)
slot!(found, e::Expr) = Expr(e.head, slot!.([found], e.args)...)
👀 Reading hidden code
494 μs
slot (generic function with 1 method)
slot(e) = let
d = Dict{Symbol,Any}()
new_e = slot!(d, e)
d, new_e
end
👀 Reading hidden code
531 μs
onestep_light(quote
(1+2) + (7-6)
2+3
4+5
sqrt(sqrt(sqrt(5)))
end |> remove_linenums) .|> slot
👀 Reading hidden code
414 ms

SlottedDisplay

We use print to turn the expression into source code.

For each line, we regex-search for slot variables, and we split the line around those. The code segments around slots are rendered inside <pre-ish> tags (like <pre> but inline), and the slots are replaced by embedded displays of the objects.

👀 Reading hidden code
356 μs
preish (generic function with 1 method)
preish(x) = @htl("<pre-ish>$(x)</pre-ish>")
👀 Reading hidden code
485 μs
SlottedDisplay
begin
struct SlottedDisplay
d
e
end
SlottedDisplay(expr) = SlottedDisplay(slot(expr)...)
end
👀 Reading hidden code
1.1 ms
function Base.show(io::IO, m::MIME"text/html", sd::SlottedDisplay)

d, e = sd.d, sd.e
s = sprint() do iobuffer
print(IOContext(iobuffer, io), e |> remove_linenums)
end
lines = split(s, "\n")
r = r"\_\_slot[a-z]{16}\_\_"
h = @htl("""<slotted-code>
$(
map(lines) do l
keys = [Symbol(m.match) for m in eachmatch(r, l)]
rest = split(l, r; keepempty=true)
result = vcat((
[(isempty(r) ? @htl("") : preish(r)), embed_display(d[k].x)]
for (r,k) in zip(rest, keys)
)...)
push!(result, preish(last(rest)))
@htl("<line-like>$(result)</line-like>")
end
)
</slotted-code>""")
show(io, m, h)
end
👀 Reading hidden code
237 ms
"slotted-code {\n\tfont-family: \"JuliaMono\", monospace;\n\tfont-size: .75rem;\n\tdisplay: flex;\n\tflex-direction: column;\n}\npre-ish {\n\twhite-space: pre;\n}\n\nline-like {\n\tdisplay: flex;\n\talign-items: baseline;\n}\n"
const slotted_code_css = """
slotted-code {
font-family: "JuliaMono", monospace;
font-size: .75rem;
display: flex;
flex-direction: column;
}
pre-ish {
white-space: pre;
}

line-like {
display: flex;
align-items: baseline;
}
"""
👀 Reading hidden code
99.7 μs
plot (generic function with 1 method)
plot(args...; kwargs...) = Hannes
👀 Reading hidden code
1.1 ms
Error message

UndefVarError: plot not defined

Stack trace

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

  1. eval
  2. 	results = Any[]		seval(ex) = Computed(Core.eval(m,ex))		# will be modified
  3. Show more...
Be patient :)
rs = @eval_step_by_step(begin
(1+2) + (7-6)
plot(2000 .+ 30 .* rand(2+2))
4+5
sqrt(sqrt(sqrt(5)))
end) .|> SlottedDisplay
👀 Reading hidden code
---
Error message

Another cell defining rs contains errors.

beep boop CRASH 🤖
@bind rindex Slider(eachindex(rs))
👀 Reading hidden code
---
Error message

Another cell defining rs and rindex contains errors.

Don't panic!
rs[rindex]
👀 Reading hidden code
---

Frame viewer

A widget that takes a series of elements and displays them as 'video frames' with a timeline scrubber.

👀 Reading hidden code
209 μs
Error message

Another cell defining rs contains errors.

Don't panic!
frames(rs)
👀 Reading hidden code
---
frames (generic function with 1 method)
function frames(fs::Vector)
l = length(fs)
startframe = l > 2 ? l - 1 : l
@htl("""
<p-frame-viewer>
<p-frames>
$(fs)
</p-frames>
<p-frame-controls>
<img src="https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.0.0/src/svg/time-outline.svg" style="width: 1em; transform: scale(-1,1); opacity: .5; margin-left: 2em;">
<input class="timescrub" style="filter: hue-rotate(149deg) grayscale(.9);" type=range min=1 max=$(l) value=$(startframe)>
</p-frame-controls>
<script>
const div = currentScript.parentElement
const input = div.querySelector("p-frame-controls > input.timescrub")
const frames = div.querySelector("p-frames")
const setviz = () => {
Array.from(frames.children).forEach((f,i) => {
f.style.display = i + 1 === input.valueAsNumber ? "inherit" : "none"
})
}
setviz()
input.addEventListener("input", setviz)

</script>



</p-frame-viewer>
<style>
$(frames_css)
</style>
""")
end
👀 Reading hidden code
7.5 ms

Macro to test frames

👀 Reading hidden code
163 μs
@visual_debug (macro with 1 method)
macro visual_debug(expr)
frames
SlottedDisplay
var"@eval_step_by_step"
quote
@eval_step_by_step($(expr)) .|> SlottedDisplay |> frames
end
end
👀 Reading hidden code
570 μs
Error message

UndefVarError: plot not defined

Stack trace

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

  1. eval
  2. 	results = Any[]		seval(ex) = Computed(Core.eval(m,ex))		# will be modified
  3. Show more...
@visual_debug begin
(1+2) + (7-6)
plot(2000 .+ 30 .* rand(2+2))
4+5
sqrt(sqrt(sqrt(5)))
end
👀 Reading hidden code
---
👀 Reading hidden code
66.7 μs

Appendix

👀 Reading hidden code
180 μs

DisplayOnly

👀 Reading hidden code
161 μs
is_inside_pluto (generic function with 1 method)
👀 Reading hidden code
627 μ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.

👀 Reading hidden code
823 μs
@only_as_script

The opposite of @skip_as_script

👀 Reading hidden code
789 μs
"hello"
@skip_as_script "hello"
👀 Reading hidden code
8.7 ms

PlutoUI favourites

👀 Reading hidden code
163 μ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
101 μs
#1 (generic function with 1 method)
👀 Reading hidden code
607 μs
toc (generic function with 1 method)
👀 Reading hidden code
522 μs
Slider
👀 Reading hidden code
466 ms
Dump (generic function with 1 method)
👀 Reading hidden code
1.8 ms
Base.RefValue{Int64}
  x: Int64 1
Dump(Ref(1))
👀 Reading hidden code
4.9 ms
Show
👀 Reading hidden code
2.4 ms

👀 Reading hidden code
65.8 μs