Skip to content

Commit

Permalink
support both structs and instances of structs as mixins, fix type det…
Browse files Browse the repository at this point in the history
…ermination in `@vars`
  • Loading branch information
hhaensel committed Jan 13, 2025
1 parent 2e5135f commit ba2234f
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 58 deletions.
48 changes: 7 additions & 41 deletions src/ReactiveTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using MacroTools
using MacroTools: postwalk
using OrderedCollections
import Genie
import Stipple: deletemode!, parse_expression!, parse_expression, init_storage, striplines, striplines!, postwalk!
import Stipple: deletemode!, parse_expression!, parse_expression, init_storage, striplines, striplines!, postwalk!, add_brackets!, required_evals!, parse_mixin_params

#definition of handlers/events
export @onchange, @onbutton, @event, @notify
Expand Down Expand Up @@ -538,22 +538,6 @@ macro define_var()
end |> esc
end

function parse_mixin_params(params)
striplines!(params)
mixin, prefix, postfix = if length(params) == 1 && params[1] isa Expr && hasproperty(params[1], :head) && params[1].head == :(::)
params[1].args[2], string(params[1].args[1]), ""
elseif length(params) == 1
params[1], "", ""
elseif length(params) == 2
params[1], string(params[2]), ""
elseif length(params) == 3
params[1], string(params[2]), string(params[3])
else
error("1, 2, or 3 arguments expected, found $(length(params))")
end
mixin, prefix, postfix
end

function parse_macros(expr::Expr, storage::LittleDict, m::Module, let_block::Expr = nothing, vars::Set = Set())
expr.head == :macrocall || return expr
flag = :nothing
Expand All @@ -562,7 +546,7 @@ function parse_macros(expr::Expr, storage::LittleDict, m::Module, let_block::Exp

source = filter(x -> x isa LineNumberNode, expr.args)
source = isempty(source) ? "" : last(source)
striplines!(expr)
expr = striplines(expr)
params = expr.args[2:end]

if fn != :mixin
Expand All @@ -579,7 +563,7 @@ function parse_macros(expr::Expr, storage::LittleDict, m::Module, let_block::Exp
storage[var] = ex
elseif fn == :mixin
mixin, prefix, postfix = parse_mixin_params(params)
mixin_storage = Stipple.model_to_storage(@eval(m, $mixin), prefix, postfix)
mixin_storage = Stipple.var_to_storage(@eval(m, $mixin), prefix, postfix; mixin_name = mixin)
pre_length = lastindex(prefix)
post_length = lastindex(postfix)

Expand Down Expand Up @@ -713,7 +697,9 @@ function get_varnames(app_expr::Vector, context::Module)
push!(varnames, res isa Symbol ? res : res[1])
elseif ex.args[1] == Symbol("@mixin")
mixin, prefix, postfix = parse_mixin_params(ex.args[2:end])
fnames = setdiff(@eval(context, collect($mixin isa LittleDict ? keys($mixin) : propertynames($mixin()))), Stipple.AUTOFIELDS, Stipple.INTERNALFIELDS)
mixin_val = @eval(context, $mixin)
mixin_val isa DataType && mixin_val <: ReactiveModel && (mixin_val = Stipple.get_concrete_type(mixin_val))
fnames = setdiff(collect(mixin_val isa LittleDict ? keys(mixin_val) : mixin_val isa DataType ? fieldnames(mixin_val) : propertynames(mixin_val)), Stipple.AUTOFIELDS, Stipple.INTERNALFIELDS)
prefix === nothing || (fnames = Symbol.(prefix, fnames, postfix))
append!(varnames, fnames)
end
Expand All @@ -725,26 +711,6 @@ function get_varnames(app_expr::Vector, context::Module)
varnames
end

function add_brackets!(expr, varnames)
expr isa Expr || return expr
ex = Stipple.find_assignment(expr)
ex === nothing && return expr
val = ex.args[end]
if val isa Symbol && val varnames
ex.args[end] = :($val[])
return expr
elseif val isa Expr
postwalk!(val) do x
if x isa Symbol && x varnames
:($x[])
else
x
end
end
end
expr
end

macro app(typename, expr, handlers_fn_name = Symbol(typename, :_handlers), mixin = false)
:(Stipple.ReactiveTools.@handlers $typename $expr $handlers_fn_name $mixin) |> esc
end
Expand Down Expand Up @@ -801,7 +767,7 @@ macro handlers(typename, expr, handlers_fn_name = Symbol(typename, :_handlers),
filter!(x -> !isa(x, LineNumberNode), initcode)
let_block = Expr(:block, :(_ = 0))
required_vars = Set()
Stipple.required_evals!.(initcode, Ref(required_vars))
required_evals!.(initcode, Ref(required_vars))
parse_macros.(initcode, Ref(storage), Ref(__module__), Ref(let_block), Ref(required_vars))
# if no initcode is provided and typename is already defined, don't overwrite the existing type and just declare the handlers function
initcode_final = isempty(initcode) && isdefined(__module__, typename) ? Expr(:block) : :(Stipple.@type($typename, $storage))
Expand Down
108 changes: 91 additions & 17 deletions src/stipple/reactivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,46 @@ function split_expr(expr)
expr.args[1] isa Symbol ? (expr.args[1], nothing, expr.args[2]) : (expr.args[1].args[1], expr.args[1].args[2], expr.args[2])
end

function model_to_storage(::Type{T}, prefix = "", postfix = "") where T# <: ReactiveModel
M = T <: ReactiveModel ? get_concrete_type(T) : T
fields = fieldnames(M)
values = getfield.(Ref(M()), fields)
function expr_isa_var(ex)
ex isa Symbol && return true
while ex isa Expr
if ex.head == :call && ex.args[1] in (:getfield, :getproperty) || ex.head == :.
ex = ex.args[2]
else
return false
end
end

return ex isa QuoteNode && ex.value isa Symbol
end

function var_to_storage(T, prefix = "", postfix = ""; mode = READONLY, mixin_name = nothing)
M, m = if T isa DataType
T <: ReactiveModel && (T = get_concrete_type(T))
T, T()
else
typeof(T), T
end

fields = collect(fieldnames(M))
values = Any[getfield.(Ref(m), fields)...]
ftypes = Any[fieldtypes(M)...]
has_reactives = any(ftypes .<: Reactive)

# if m has no reactive fields, we assume that all fields should be made reactive, default mode is READONLY
if !has_reactives
for (i, (f, type, v)) in enumerate(zip(fields, values, ftypes, values))
f in [INTERNALFIELDS..., AUTOFIELDS...] && continue
rtype = Reactive{type}
ftypes[i] = rtype
values[i] = expr_isa_var(mixin_name) ? Expr(:call, rtype, Expr(:., mixin_name, QuoteNode(f)), mode) : rtype(v, mode)
end
end
storage = LittleDict{Symbol, Expr}()
for (f, type, v) in zip(fields, fieldtypes(M), values)
for (f, type, v) in zip(fields, ftypes, values)
f = f in [INTERNALFIELDS..., AUTOFIELDS...] ? f : Symbol(prefix, f, postfix)
storage[f] = v isa Symbol ? :($f::$type = $(QuoteNode(v))) : :($f::$type = Stipple._deepcopy($v))
v isa Symbol && (v = QuoteNode(v))
storage[f] = v isa QuoteNode || v isa Expr ? :($f::$type = $v) : :($f::$type = Stipple._deepcopy($v))
end
# fix channel field, which is not reconstructed properly by the code above
storage[:channel__] = :(channel__::String = Stipple.channelfactory())
Expand Down Expand Up @@ -374,6 +406,42 @@ end

parse_expression(expr::Expr, mode = nothing, source = nothing, m = nothing, let_block::Union{Expr, Nothing} = nothing, vars::Set = Set()) = parse_expression!(copy(expr), mode, source, m, let_block, vars)

function parse_mixin_params(params)
striplines!(params)
mixin, prefix, postfix = if length(params) == 1 && params[1] isa Expr && hasproperty(params[1], :head) && params[1].head == :(::)
params[1].args[2], string(params[1].args[1]), ""
elseif length(params) == 1
params[1], "", ""
elseif length(params) == 2
params[1], string(params[2]), ""
elseif length(params) == 3
params[1], string(params[2]), string(params[3])
else
error("1, 2, or 3 arguments expected, found $(length(params))")
end
mixin, prefix, postfix
end

function add_brackets!(expr, varnames)
expr isa Expr || return expr
ex = Stipple.find_assignment(expr)
ex === nothing && return expr
val = ex.args[end]
if val isa Symbol && val varnames
ex.args[end] = :($val[])
return expr
elseif val isa Expr
postwalk!(val) do x
if x isa Symbol && x varnames
:($x[])
else
x
end
end
end
expr
end

macro var_storage(expr)
m = __module__
if expr.head != :block
Expand All @@ -386,6 +454,7 @@ macro var_storage(expr)
required_vars = Set()
let_block = Expr(:block, :(_ = 0))
required_evals!.(expr.args, Ref(required_vars))
add_brackets!.(expr.args, Ref(required_vars))
for e in expr.args
if e isa LineNumberNode
source = e
Expand Down Expand Up @@ -415,17 +484,22 @@ macro var_storage(expr)
else
# parse @mixin call, which is now only defined in ReactiveTools, but wouldn't work here
if e.head == :macrocall && (e.args[1] == Symbol("@mixin") || e.args[1] == Symbol("@mix_in"))
e.args = filter!(x -> ! isa(x, LineNumberNode), e.args)
prefix = postfix = ""
if e.args[2] isa Expr && e.args[2].head == :(::)
prefix = string(e.args[2].args[1])
e.args[2] = e.args[2].args[2]
else
length(e.args) 3 && (prefix = string(e.args[3]))
length(e.args) 4 && (postfix = string(e.args[4]))
end

mixin_storage = @eval __module__ Stipple.model_to_storage($(e.args[2]), $prefix, $postfix)
# e.args = filter!(x -> ! isa(x, LineNumberNode), e.args)
# prefix = postfix = ""
# if e.args[2] isa Expr && e.args[2].head == :(::)
# prefix = string(e.args[2].args[1])
# e.args[2] = e.args[2].args[2]
# else
# length(e.args) ≥ 3 && (prefix = string(e.args[3]))
# length(e.args) ≥ 4 && (postfix = string(e.args[4]))
# end

# mixin = e.args[2]
params = e.args[2:end]
mixin, prefix, postfix = parse_mixin_params(params)
mixin_storage = Stipple.var_to_storage(@eval(__module__, $mixin), prefix, postfix; mixin_name = mixin)

# mixin_storage = Stipple.var_to_storage(@eval(__module__, $mixin), prefix, postfix; mixin_name = mixin)
storage = merge_storage(storage, mixin_storage; context = __module__)
end
:modes__, e
Expand Down

0 comments on commit ba2234f

Please sign in to comment.