Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversion of VarName to/from string #100

Merged
merged 22 commits into from
Oct 1, 2024
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.
penelopeysm marked this conversation as resolved.
Show resolved Hide resolved
"""
function vn_from_string(str)
new_fields = eval(Meta.parse(str))
return VarName{Symbol(new_fields.sym)}(nt_to_optic(new_fields.optic))
end
penelopeysm marked this conversation as resolved.
Show resolved Hide resolved
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