Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/AbstractPPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export VarName,
subsumes,
subsumedby,
varname,
vn_to_string,
vn_from_string,
vsym,
@varname,
@vsym
Expand Down
110 changes: 109 additions & 1 deletion src/varname.jl
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ subsumes(t::ComposedOptic, u::ComposedOptic) =
# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
# leaf of the "lens-tree".
subsumes(t::ComposedOptic, u::PropertyLens) = false
# Here we need to check if `u.outer` (i.e. the next lens to be applied from `u`) is
# Here we need to check if `u.inner` (i.e. the next lens to be applied from `u`) is
# subsumed by `t`, since this would mean that the rest of the composition is also subsumed
# by `t`.
subsumes(t::PropertyLens, u::ComposedOptic) = subsumes(t, u.inner)
Expand Down Expand Up @@ -450,6 +450,7 @@ struct ConcretizedSlice{T,R} <: AbstractVector{T}
end

ConcretizedSlice(s::Base.Slice{R}) where {R} = ConcretizedSlice{eltype(s.indices),R}(s.indices)
ConcretizedSlice(s::Base.OneTo{R}) where {R} = ConcretizedSlice(Base.Slice(s))
Base.show(io::IO, s::ConcretizedSlice) = print(io, ":")
Base.show(io::IO, ::MIME"text/plain", s::ConcretizedSlice) =
print(io, "ConcretizedSlice(", s.range, ")")
Expand Down Expand Up @@ -747,3 +748,110 @@ function vsym(expr::Expr)
error("Malformed variable name `$(expr)`!")
end
end

"""
index_to_str(i)

Generates a string representation of the index `i`, or a tuple thereof.

## Examples

```jldoctest
julia> index_to_str(2)
"2"

julia> index_to_str((1, 2:5))
"(1, 2:5,)"

julia> index_to_str(:)
":"

julia> index_to_str(ConcretizedSlice(Base.Slice(Base.OneTo(10))))
"ConcretizedSlice(Base.OneTo(10))"
```
"""
index_to_str(i::Integer) = string(i)
index_to_str(r::UnitRange) = "$(first(r)):$(last(r))"
index_to_str(::Colon) = ":"
index_to_str(s::ConcretizedSlice{T,R}) where {T,R} = "ConcretizedSlice(" * repr(s.range) * ")"
index_to_str(t::Tuple) = "(" * join(map(index_to_str, t), ", ") * ",)"

"""
optic_to_nt(optic)

Convert an optic to a named tuple representation.

## Examples
```jldoctest; setup=:(using Accessors)
julia> optic_to_nt(identity)
(type = "identity",)

julia> optic_to_nt(@optic _.a)
(type = "property", field = "a")

julia> optic_to_nt(@optic _.a.b)
(type = "composed", outer = (type = "property", field = "b"), inner = (type = "property", field = "a"))

julia> optic_to_nt(@optic _[1]) # uses index_to_str()
(type = "index", indices = "(1,)")
```
"""
optic_to_nt(::typeof(identity)) = (type = "identity",)
optic_to_nt(::PropertyLens{sym}) where {sym} = (type = "property", field = String(sym))
optic_to_nt(i::IndexLens) = (type = "index", indices = index_to_str(i.indices))
optic_to_nt(c::ComposedOptic) = (type = "composed", outer = optic_to_nt(c.outer), inner = optic_to_nt(c.inner))


"""
nt_to_optic(nt)

Convert a named tuple representation back to an optic.
"""
function nt_to_optic(nt)
if nt.type == "identity"
return identity
elseif nt.type == "index"
return IndexLens(eval(Meta.parse(nt.indices)))
elseif nt.type == "property"
return PropertyLens{Symbol(nt.field)}()
elseif nt.type == "composed"
return nt_to_optic(nt.outer) ∘ nt_to_optic(nt.inner)
end
end

"""
vn_to_string(vn::VarName)

Convert a `VarName` as a string, via an intermediate named tuple. This differs
from `string(vn)` in that concretised slices are faithfully represented (rather
than being pretty-printed as colons).

```jldoctest
julia> vn_to_string(@varname(x))
"(sym = \"x\", optic = (type = \"identity\",))"

julia> vn_to_string(@varname(x.a))
"(sym = \"x\", optic = (type = \"property\", field = \"a\"))"

julia> y = ones(2); vn_to_string(@varname(y[:]))
"(sym = \"y\", optic = (type = \"index\", indices = \"(:,)\"))"

julia> y = ones(2); vn_to_string(@varname(y[:], true))
"(sym = \"y\", optic = (type = \"index\", indices = \"(ConcretizedSlice(Base.OneTo(2)),)\"))"
```
"""
vn_to_string(vn::VarName) = repr((sym = String(getsym(vn)), optic = optic_to_nt(getoptic(vn))))

"""
vn_from_string(str)

Convert a string representation of a `VarName` back to a `VarName`. The string
should have been generated by `vn_to_string`.

NOTE: This function should only be used with trusted input, as it uses `eval`
and `Meta.parse` to parse the string.
"""
function vn_from_string(str)
new_fields = eval(Meta.parse(str))
return VarName{Symbol(new_fields.sym)}(nt_to_optic(new_fields.optic))
end
28 changes: 28 additions & 0 deletions test/varname.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,32 @@ end
@inferred get(c, @varname(b.a[1]))
@inferred Accessors.set(c, @varname(b.a[1]), 10)
end

@testset "roundtrip conversion to/from string" begin
y = ones(10)
vns = [
@varname(x),
@varname(ä),
@varname(x.a),
@varname(x.a.b),
@varname(var"x.a"),
@varname(x[1]),
@varname(var"x[1]"),
@varname(x[1:10]),
@varname(x[1, 2]),
@varname(x[1, 2:5]),
@varname(x[:]),
@varname(x.a[1]),
@varname(x.a[1:10]),
@varname(x[1].a),
@varname(y[:]),
@varname(y[begin:end]),
@varname(y[end]),
@varname(y[:], false),
@varname(y[:], true),
]
for vn in vns
@test vn_from_string(vn_to_string(vn)) == vn
end
end
end
Loading