|
346 | 346 | Base.@propagate_inbounds function set(obj, lens::DynamicIndexLens, val)
|
347 | 347 | return setindex(obj, val, lens.f(obj)...)
|
348 | 348 | end
|
| 349 | + |
| 350 | +abstract type AbstractQuery end |
| 351 | + |
| 352 | +struct Query{F1,F2} <: AbstractQuery |
| 353 | + select::F1 |
| 354 | + ignore::F2 |
| 355 | +end |
| 356 | +Query(select) = Query(select, x -> false) |
| 357 | +Query(; select, ignore=x -> false) = Query(select, ignore) |
| 358 | + |
| 359 | +queryargs(::Query, obj, fn, val) = (val,) |
| 360 | + |
| 361 | +struct ParentQuery{F1,F2} <: AbstractQuery |
| 362 | + select::F1 |
| 363 | + ignore::F2 |
| 364 | +end |
| 365 | +ParentQuery(select) = ParentQuery(select, x -> false) |
| 366 | +ParentQuery(; select, ignore=x -> false) = ParentQuery(select, ignore) |
| 367 | + |
| 368 | +queryargs(::ParentQuery, obj, fn, val) = (obj, fn) |
| 369 | + |
| 370 | +@inline (optic::AbstractQuery)(obj) = _getall(obj, optic) |
| 371 | +@inline set(obj, optic::AbstractQuery, xs) = first(first(_setall(obj, optic, xs))) |
| 372 | + |
| 373 | +struct Changed end |
| 374 | +struct Unchanged end |
| 375 | + |
| 376 | +@inline function modify(f, obj, optic::AbstractQuery) |
| 377 | + set(obj, optic, map(f, optic(obj))) |
| 378 | +end |
| 379 | + |
| 380 | +@generated function _getall(obj::O, optic::AbstractQuery) where O |
| 381 | + exp = Expr(:tuple) |
| 382 | + for fieldname in fieldnames(O) |
| 383 | + v = quote |
| 384 | + fn = $(QuoteNode(fieldname)) |
| 385 | + val = getfield(obj, fn) |
| 386 | + args = queryargs(optic, obj, fn, val) |
| 387 | + if optic.select(args...) |
| 388 | + (val,) |
| 389 | + elseif !optic.ignore(args...) |
| 390 | + _getall(val, optic) |
| 391 | + else |
| 392 | + () |
| 393 | + end |
| 394 | + end |
| 395 | + # Splat the result into the output tuple |
| 396 | + push!(exp.args, Expr(:..., v)) |
| 397 | + end |
| 398 | + exp |
| 399 | +end |
| 400 | + |
| 401 | +@generated function _setall(obj::O, optic::AbstractQuery, xs, n=1) where O |
| 402 | + exp = Expr(:tuple) |
| 403 | + for fieldname in fieldnames(O) |
| 404 | + v = quote |
| 405 | + fn = $(QuoteNode(fieldname)) |
| 406 | + val = getfield(obj, fn) |
| 407 | + args = queryargs(optic, obj, fn, val) |
| 408 | + if optic.select(args...) |
| 409 | + pair = xs[n] => Changed() |
| 410 | + n += 1 |
| 411 | + pair |
| 412 | + elseif !optic.ignore(args...) |
| 413 | + pair, n = _setall(val, optic, xs, n) |
| 414 | + pair |
| 415 | + else |
| 416 | + val => Unchanged() |
| 417 | + end |
| 418 | + end |
| 419 | + push!(exp.args, v) |
| 420 | + end |
| 421 | + quote |
| 422 | + pairs = $exp |
| 423 | + vals = map(first, pairs) |
| 424 | + changes = map(last, pairs) |
| 425 | + _maybeconstruct(obj, vals, changes), n |
| 426 | + end |
| 427 | +end |
| 428 | + |
| 429 | +@generated function _maybeconstruct(obj, vals, changes::C) where C |
| 430 | + if Changed in fieldtypes(C) |
| 431 | + :(_maybeconstruct(Changed(), obj, vals)) |
| 432 | + else |
| 433 | + :(_maybeconstruct(Unchanged(), obj, vals)) |
| 434 | + end |
| 435 | +end |
| 436 | +_maybeconstruct(::Changed, obj::O, vals) where O = constructorof(O)(vals...) => Changed() |
| 437 | +_maybeconstruct(::Unchanged, obj, vals) = obj => Unchanged() |
0 commit comments