Skip to content

Commit

Permalink
Merge pull request #37 from fonsp/new-reactivity
Browse files Browse the repository at this point in the history
✨ New reactivity
  • Loading branch information
fonsp authored Mar 27, 2020
2 parents 4d941d4 + 166263d commit d713381
Show file tree
Hide file tree
Showing 13 changed files with 591 additions and 296 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "Pluto"
uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781"
license = "MIT"
authors = ["Fons van der Plas <[email protected]>", "Mikołaj Bochenski <[email protected]>"]
version = "0.3.6"
version = "0.4.0"

[deps]
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Expand Down
5 changes: 3 additions & 2 deletions src/Pluto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ const VERSION_STR = 'v' * Pkg.TOML.parsefile(joinpath(PKG_ROOT_DIR, "Project.tom
https://github.com/fonsp/Pluto.jl
\n"""

include("./react/ExploreExpression.jl")
using .ExploreExpression
include("./react/Cell.jl")
include("./react/Notebook.jl")
include("./react/ExploreExpression.jl")
include("./react/ModuleManager.jl")
include("./react/WorkspaceManager.jl")
include("./react/React.jl")

include("./webserver/NotebookServer.jl")
Expand Down
9 changes: 6 additions & 3 deletions src/react/Cell.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ mutable struct Cell
output::Any
runtime::Union{Missing,UInt64}
errormessage::Any
modified_symbols::Set{Symbol}
referenced_symbols::Set{Symbol}
symstate::SymbolsState
resolved_funccalls::Set{Symbol}
resolved_symstate::SymbolsState
module_usings::Set{Expr}
end

Cell(uuid, code) = Cell(uuid, code, nothing, nothing, missing, nothing, SymbolsState(), Set{Symbol}(), SymbolsState(), Set{Expr}())

"Turn a `Cell` into an object that can be serialized using `JSON.json`, to be sent to the client."
function serialize(cell::Cell)
Dict(:uuid => string(cell.uuid), :code => cell.code)# , :output => cell.output)
end

createcell_fromcode(code::String) = Cell(uuid1(), code, nothing, nothing, missing, nothing, Set{Symbol}(), Set{Symbol}(), Set{Expr}())
createcell_fromcode(code::String) = Cell(uuid1(), code)

function relay_output!(cell::Cell, output::Any)
cell.output = output
Expand Down
86 changes: 64 additions & 22 deletions src/react/ExploreExpression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,49 @@ const modifiers = [:(+=), :(-=), :(*=), :(/=), :(//=), :(^=), :(÷=), :(%=), :(<
mutable struct SymbolsState
references::Set{Symbol}
assignments::Set{Symbol}
funccalls::Set{Symbol}
funcdefs::Dict{Symbol,SymbolsState}
end

SymbolsState(references, assignments, funccalls) = SymbolsState(references, assignments, funccalls, Dict{Symbol,SymbolsState}())
SymbolsState(references, assignments) = SymbolsState(references, assignments, Set{Symbol}())
SymbolsState() = SymbolsState(Set{Symbol}(), Set{Symbol}())

"ScopeState moves _up_ the ASTree: it carries scope information up towards the endpoints"
mutable struct ScopeState
inglobalscope::Bool
exposedglobals::Set{Symbol}
hiddenglobals::Set{Symbol}
end

function union(a::Dict{Symbol,SymbolsState}, b::Dict{Symbol,SymbolsState})
c = Dict{Symbol,SymbolsState}()
for (k, v) in a
c[k] = v
end
for (k, v) in b
c[k] = v
end
c
end

function union(a::SymbolsState, b::SymbolsState)
SymbolsState(a.references b.references, a.assignments b.assignments)
SymbolsState(a.references b.references, a.assignments b.assignments, a.funccalls b.funccalls, a.funcdefs b.funcdefs)
end

function union(a::ScopeState, b::ScopeState)
SymbolsState(a.inglobalscope && b.inglobalscope, a.exposedglobals b.exposedglobals, a.hiddenglobals b.hiddenglobals)
end

function ==(a::SymbolsState, b::SymbolsState)
a.references == b.references && a.assignments == b.assignments
a.references == b.references && a.assignments == b.assignments&& a.funccalls == b.funccalls && a.funcdefs == b.funcdefs
end

function will_assign_global(assignee::Symbol, scopestate::ScopeState)::Bool
(scopestate.inglobalscope || assignee in scopestate.exposedglobals) && !(assignee in scopestate.hiddenglobals)
end

function get_global_assignees(assignee_exprs, scopestate::ScopeState)
function get_global_assignees(assignee_exprs, scopestate::ScopeState)::Set{Symbol}
global_assignees = Set{Symbol}()
for ae in assignee_exprs
if isa(ae, Symbol)
Expand Down Expand Up @@ -88,7 +105,7 @@ end
# 1 is a value (Int64)
function explore!(value, scopestate::ScopeState)::SymbolsState
# includes: LineNumberNode, Int64, String,
return SymbolsState(Set{Symbol}(), Set{Symbol}())
return SymbolsState(Set{Symbol}(), Set{Symbol}(), Set{Symbol}(), Dict{Symbol,SymbolsState}())
end

# Possible leaf: symbol
Expand All @@ -98,16 +115,16 @@ end
# Therefore, this method only handles _references_, which are added to the symbolstate, depending on the scopestate.
function explore!(sym::Symbol, scopestate::ScopeState)::SymbolsState
return if !(sym in scopestate.hiddenglobals)
SymbolsState(Set([sym]), Set{Symbol}())
SymbolsState(Set([sym]), Set{Symbol}(), Set{Symbol}(), Dict{Symbol,SymbolsState}())
else
SymbolsState(Set{Symbol}(), Set{Symbol}())
SymbolsState(Set{Symbol}(), Set{Symbol}(), Set{Symbol}(), Dict{Symbol,SymbolsState}())
end
end

# General recursive method. Is never a leaf.
# Modifies the `scopestate`.
function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
symstate = SymbolsState(Set{Symbol}(), Set{Symbol}())
symstate = SymbolsState(Set{Symbol}(), Set{Symbol}(), Set{Symbol}(), Dict{Symbol,SymbolsState}())
if ex.head == :(=)
# Does not create scope

Expand All @@ -117,7 +134,7 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
elseif isa(ex.args[1], Expr)
if ex.args[1].head == :tuple
# (x, y) = (1, 23)
filter(s -> s isa Symbol, ex.args[1].args)
filter(s->s isa Symbol, ex.args[1].args)
elseif ex.args[1].head == :(::)
# TODO: type is referenced
[ex.args[1].args[1]]
Expand Down Expand Up @@ -223,34 +240,51 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
global_assignees = get_global_assignees([funcname], scopestate)

# 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.hiddenglobals = union(innerscopestate.hiddenglobals, extractfunctionarguments(funcroot))
innerscopestate.inglobalscope = false
for a in ex.args[2:end]
innersymstate = explore!(a, innerscopestate)

innersymstate = explore!(Expr(:block, ex.args[2:end]...), innerscopestate)

if funcname in global_assignees
symstate.funcdefs[funcname] = innersymstate
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 = symstate innersymstate
end

scopestate.hiddenglobals = union(scopestate.hiddenglobals, global_assignees)
symstate.assignments = union(symstate.assignments, global_assignees)

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]


# 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.hiddenglobals = union(innerscopestate.hiddenglobals, extractfunctionarguments(funcroot))
innerscopestate.inglobalscope = false
for a in ex.args[2:end]
innersymstate = explore!(a, innerscopestate)
symstate = symstate innersymstate
args_ex = if funcroot isa Symbol || (funcroot isa Expr && funcroot.head == :(::))
[funcroot]
elseif funcroot.head == :tuple
funcroot.args
else
@error "Unknown lambda type"
end

return symstate
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

Expand Down Expand Up @@ -350,6 +384,14 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
symstate = innersymstate SymbolsState(Set{Symbol}([Symbol("@md_str")]), Set{Symbol}())


return symstate
elseif ex.head == :call && ex.args[1] isa Symbol
# Does not create scope

# We change the `call` to a `block` and recurse again (hitting the fallback below).
# In particular, this adds the called function as a reference, which is what we want.
symstate = explore!(Expr(:block, ex.args...), scopestate)
push!(symstate.funccalls, ex.args[1])
return symstate
else
# fallback, includes:
Expand All @@ -368,7 +410,7 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
end


function compute_symbolreferences(ex)
function compute_symbolreferences(ex)::SymbolsState
explore!(ex, ScopeState(true, Set{Symbol}(), Set{Symbol}()))
end

Expand Down
86 changes: 0 additions & 86 deletions src/react/ModuleManager.jl

This file was deleted.

18 changes: 10 additions & 8 deletions src/react/Notebook.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ using UUIDs

mutable struct Notebook
path::String

"Cells are ordered in a `Notebook`, and this order can be changed by the user. Cells will always have a constant UUID."
cells::Array{Cell,1}

uuid::UUID
combined_funcdefs::Union{Nothing,Dict{Symbol, SymbolsState}}

# buffer must contain all undisplayed outputs
pendingupdates::Channel
end
# We can keep 128 updates pending. After this, any put! calls (i.e. calls that push an update to the notebook) will simply block, which is fine.
# This does mean that the Notebook can't be used if nothing is clearing the update channel.
Notebook(path::String, cells::Array{Cell,1}, uuid) = Notebook(path, cells, uuid, Channel(128))
Notebook(path::String, cells::Array{Cell,1}) = Notebook(path, cells, uuid4(), Channel(128))
Notebook(path::String, cells::Array{Cell,1}, uuid) = Notebook(path, cells, uuid, nothing, Channel(128))
Notebook(path::String, cells::Array{Cell,1}) = Notebook(path, cells, uuid4(), nothing, Channel(128))

function selectcell_byuuid(notebook::Notebook, uuid::UUID)::Union{Cell,Nothing}
cellIndex = findfirst(c->c.uuid == uuid, notebook.cells)
Expand All @@ -29,7 +31,7 @@ _uuid_delimiter = "# ⋐⋑ "
_order_delimited = "# ○ "
_cell_appendix = "\n\n"

emptynotebook(path) = Notebook(path, [createcell_fromcode("")], uuid4())
emptynotebook(path) = Notebook(path, [createcell_fromcode("")])
emptynotebook() = emptynotebook(tempname() * ".jl")

function samplenotebook()
Expand All @@ -51,7 +53,7 @@ function samplenotebook()
Apparently we had reached a great height in the atmosphere, for the sky was a dead black, and the stars had ceased to twinkle. By the same illusion which lifts the horizon of the sea to the level of the spectator on a hillside, the sable cloud beneath was dished out, and the car seemed to float in the middle of an immense dark sphere, whose upper half was strewn with silver. Looking down into the dark gulf below, I could see a ruddy light streaming through a rift in the clouds."
"""))

Notebook(tempname() * ".jl", cells, uuid4())
Notebook(tempname() * ".jl", cells)
end

function save_notebook(io, notebook)
Expand Down Expand Up @@ -110,7 +112,7 @@ function load_notebook(io, path)
# Change windows line endings to linux; remove the cell appendix.
code_normalised = replace(code, "\r\n" => "\n")[1:end - ncodeunits(_cell_appendix)]

read_cell = Cell(uuid, code_normalised, nothing, nothing, missing, nothing, Set{Symbol}(), Set{Symbol}(), Set{Expr}())
read_cell = Cell(uuid, code_normalised)

collected_cells[uuid] = read_cell
end
Expand All @@ -129,7 +131,7 @@ function load_notebook(io, path)
end
end

Notebook(path, ordered_cells, uuid4())
Notebook(path, ordered_cells)
end

function load_notebook(path::String)
Expand Down
Loading

2 comments on commit d713381

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on d713381 Mar 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/11687

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.0 -m "<description of version>" d7133814da3c673749ba876bd5b6ac8a0b3cff23
git push origin v0.4.0

Please sign in to comment.